The issue I am having is getting the price of the option within the Selection model. This is because, depending on which other Options are also within the same cart, different prices will be used to generate the total. I need help with a queryset that gets me the price of the Option, if the option has an effector_option that itself is within the same cart, used that, otherwise use the Variation with only the option field set.
TempName App model consists of:
class Section(models.Model):
title = models.CharField(max_length=20)
description = models.CharField(max_length=100)
temp = models.ForeignKey(TempName, null=False)
def __str__(self):
return self.title
def get_options(self):
return self.option_set.all()
class Option(models.Model):
name = models.CharField(max_length=120)
section = models.ForeignKey(Section, null=False)
def __str__(self):
return self.name
def get_variations(self):
return self.variation_set.all()
class Variation(models.Model):
name = models.CharField(max_length=60, blank=True, unique=True)
price = models.DecimalField(max_digits=5, decimal_places=2)
option = models.ForeignKey(Option, null=False)
effector_option = models.ForeignKey(Option, null=True, blank=True, related_name='option_effected')
def __str__(self):
return self.name
There can be many Sections on one page. Each Section can contain many Options which will later be selectable by the user. The selected Option will go in the cart which will be used to generate the total price.
Within Variation model, the field option simply tells me which option the Variation belongs to. The effector_option field within the Varaition model however will be used by the cart.
The user will be allowed to select any number of Options, however, depending on the Options the user selected, other options may display the Variation price where effector_option was previously selected.
Cart App model consists of:
class Cart(models.Model):
owner = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True)
creation_date = models.DateTimeField(verbose_name='creation date')
checked_out = models.BooleanField(default=False, verbose_name='checked out')
class Meta:
verbose_name = 'cart'
verbose_name_plural = 'carts'
ordering = ('-creation_date',)
def __str__(self):
return unicode(self.creation_date)
def get_selections(self):
return self.selection_set.all()
class Selection(models.Model):
cart = models.ForeignKey(Cart)
option = models.ForeignKey(Option)
@property
def price(self):
return 10
The cart can contain multiple Options which the user has selected. Each Selection will have a price property which can be used to show the user the price of that individual selection depending on which other Option is also in the same cart.
WHAT I HAVE TRIED:
Get all Variations of the option. Then loop through each variation and check if the Variation.effector_option is contained within the same Cart. If so display that price, otherwise display the price within the variation where Variation.effector_option is null/ not set.
What I found was that more than 26 queries were being called for each Selection in the cart. Does this db schema need more normalizing or is it good enough for this simple project?
What I found was that more than 26 queries were being called for each Selection in the cart. Does this db schema need more normalizing or is it good enough for this simple project?
I'll bite because I believe the solution to this is software engineering. The issue here is not normalization, nor is it Django. Its how you've chosen to organize the problem. I surmise you have fallen into a few traps which I'll discuss as we go. First let's get some definitions straight here starting with your own...
Now we have this issue right? Some of my Option
selections can be used with other Option
selections and affect the price! Chaos! We need Variation
to provide meta rules for their interactions. More Queries! More logic! Maybe...
Trap 1
Letting the way things look on the screen drive your data model
I'm guessing here but the use of words Section
and Option
make me feel like you are organizing a screen and not a database. More commonly businesses have models that look like the following...
Product
- Something we sell(name
, base_price
, options
, option_sets
)OptionSet
- Groups of options that go together as a package deal! Think of the savings!(name
, group
, options
, price
, optionset_exclusions
, option_exclusions
)Option
- a la carte options(name
, group
, price
) Purchase
- What someone wants to buy (product
, options
, optionsets
)Now you might be saying "What about my sections!" Sections could be something as easy as hanging a piece of metadata off OptionSet
and Option
called group
with a CharField
type. When rendering in your template you can group options/optionsets together by group
. You can use exclusions
to stop people from selecting conflicting Option
and OptionSets
for a Product
. Now this whole shebang can be plopped on the page with as few as 3 queries(with proper use of prefetch_related
) and selected Options
/OptionSets
can simply be added together to get a deterministic price.
Trap 2
Letting the way you want it to work prevent it from working at all
Now before you launch the salvo of "This can't work for me, I'm a snowflake!"(been here a long time). Often we find that the way we want something to work stands in the way of it working.
The old unix heads used to debate the merits of Consistency, Simplicity, and Completeness. The consensus is that Simplicity is best even if its not Complete or Consistent. Your original solution looks to use complexity to achieve Completeness. It's a trap!(thanks admiral ackbar)
e.g. this is the way my/my client's business works so it has to work this way
Software is cheaper/easier to write when we look for ways to achieve Simplicity. Sometimes this means changing the organization to conform to the limitations of the software.
I can imagine a retort to the above that states
Option 1 will give you a 10% discount on Option 2. You just have static prices!.
This can be modeled in the schema above where the total price equals Option 1's Price + .9(Option 2's prices). In this case we've just taken the notion of Variation
and made it data instead of behavior. Much simpler. Its more flexible to. I mean you could do some complex 3D Volumetric Calculus on prices and just render the result into your product schema. There are more problems to consider...
I don't want to do all this configuration by hand!
Write yourself a Django management command that imports from a spreadsheet.
What if prices or relationships between Options
change?
Most product/price schema's include a notion of _spec
like Option_spec
that allows you to capture Point In Time purchase terms. _spec
records are connected to the Purchase
at the time things are bought. This allows Option
and OptionSet
to change without changing all the connected past purchases.
and the list goes on...
The point here is that all the problems you can dream of have simple solutions if you are clever and open minded.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With