Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django: Checking if object exists in queryset (IF ELSE)

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?

like image 755
Erion S Avatar asked Dec 19 '16 21:12

Erion S


1 Answers

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...

  • Section - A group of options
  • Option - A price
  • Variation - An attempt to model options that affect other options

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.

like image 80
nsfyn55 Avatar answered Sep 20 '22 03:09

nsfyn55