Django models - related objects validation - python

I'm wondering how to have validations for related objects. To my surprise I haven't found much relevant information on this.
For example:
class Listing(models.Model):
categories = models.ManyToManyField('Category')
price_sale = models.DecimalField(max_digits=8, decimal_places=0, null=True)
price_rent = models.DecimalField(max_digits=8, decimal_places=0, null=True)
price_vacation = models.DecimalField(max_digits=8, decimal_places=0, null=True)
class Category(models.Model):
value = models.CharField(max_length=32)
class Image(models.Model):
listing = models.ForeignKey('Listing')
image = models.ImageField(upload_to=get_file_path)
How can I make sure that at least one category is set, there are no
duplicates for the listing?
How can I make sure that if one of the categories is 'sale', price_sale must be set or else set to null?
How can I make sure that at least one image is inserted but not more
than say 10 images?
I'm thinking this should be done in the model in case I choose to input data aside from forms (something like parsing a feed), would this be correct? I tried dealing with clean() but it requires a PK before letting me deal with m2m relationships, etc.
Bonus question: Why would I choose to limit a field using choices rather than limiting by FK?

Try explicitly creating your mapping table, and have your ManyToMany relationship go through this model. Since it is a normal Django model you should be able to define most of your validation logic within its clean method.
class Listing(models.Model):
categories = models.ManyToManyField('Category', through='CategoryListing')
price_sale = models.DecimalField(max_digits=8, decimal_places=0, null=True)
price_rent = models.DecimalField(max_digits=8, decimal_places=0, null=True)
price_vacation = models.DecimalField(max_digits=8, decimal_places=0, null=True)
class Category(models.Model):
value = models.CharField(max_length=32)
class CategoryListing(models.Model):
category = models.ForeignKey(Category)
listing = models.ForeignKey(Listing)
def clean(self):
# validation logic
https://docs.djangoproject.com/en/1.3/topics/db/models/#intermediary-manytomany

Related

How to do SELECT COUNT(*) GROUP BY of ForeignKey field in Django?

Let's say we have three models:
class Category(models.Model):
name = models.CharField(max_length=255)
class Platform(models.Model):
name = models.CharField(max_length=255)
class Product(models.Model):
name = models.CharField(max_length=255)
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='products')
platform = models.ForeignKey(Platform, on_delete=models.CASCADE, related_name='products')
class SellingItem(models.Model):
name = models.CharField(max_length=255)
product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name='selling_items')
price = models.DecimalField(max_digits=5, decimal_places=2)
The idea here is to get a total of the different categories and platforms based on a SellingItem's queryset.
My initial approach is:
categories = queryset.values('product__category__id', 'product__category__name', 'product__category__label').annotate(total=Count('product__category__id')) # noqa: E501
platforms = queryset.exclude(product__platform__isnull=True).values('product__platform__id', 'product__platform__name', 'product__platform__label').annotate(total=Count('product__platform__id')) # noqa: E501
The problem is that the result is not grouped...
The code is working on based on a Product's queryset if removing all product__ in the previous code.
Your approach towards the problem is absolutely correct. Although you are missing a small thing at the end which is '.order_by()'. If you add it, you should get your desired result. What this does is that it will tell django to not apply any type of ordering. Thus, you will achieve your result.
This issue occurred because django tried to apply default ordering it has thus your queryset didn't output the desired results.
categories = queryset.values('product__category__id', 'product__category__name', 'product__category__label').annotate(total=Count('product__category__id')).order_by() # order_by at the end.
platforms = queryset.exclude(product__platform__isnull=True).values('product__platform__id', 'product__platform__name', 'product__platform__label').annotate(total=Count('product__platform__id')).order_by() # order_by at the end.

How to process data from one model field to another

I have models of Exercise, Training and Workout.
Training contains some exercises (Exercise)
Workout contains trainings (Training).
Snippet of my models.py:
class Exercise(models.Model):
user = models.ForeignKey(User, related_name='exercises',
on_delete=models.CASCADE)
name = models.CharField(max_length=80)
description = models.TextField(max_length=300)
details = models.ManyToManyField(ExerciseDetail, blank=True)
...
class Training(models.Model):
user = models.ForeignKey(User, related_name='trainings',
on_delete=models.CASCADE)
name = models.CharField(max_length=80)
description = models.CharField(max_length=250)
exercises = models.ManyToManyField(Exercise, related_name='trainings',
blank=True)
...
class Workout(models.Model):
user = models.ForeignKey(User, related_name='workouts',
on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now=True)
name = models.CharField(max_length=200)
description = models.TextField(max_length=400, blank=True)
trainings = models.ManyToManyField(Training, related_name='workouts',
blank=True)
...
I would like to have possibility to use something like Workout.objects.get(name='workout').exercises.objects.all() to get a list/set of all exercises included in trainings of chosen Workout.
I would also like to have possibility to use exercises`` field with Django Rest Framework to list all exercises, possibly with link to particularExercise``` model serializer.
Can someone give a hint how can I do that?
You can query this with:
Exercise.objects.filter(
trainings__workouts__name='workout'
)
With the consecutive underscores (__), you thus can look "through" relations.
This will thus return the Exercises that belong to Trainings that belong to Workouts with as name 'Workout'.

GenericForeignKey or ForeignKey

I currently have to make the following decision for my model Order: Either I use GenericForeignKey which refers either to the models DiscountModel or AnotherDiscountModel. There can only be one of those, so from the idea GenericForeignKey could make sense.
However, I am implementing it somewhere, where performance matters. The alternative approach would be to have to ForeignKey fields in my model: discount_model and another_discount_model. One of them will always be empty.
I now wonder which path you would go before I add the "other discount model". Do you have any insights you can share with me? Currently, GenericForeignKey seems a bit more complex to me and I would have to change several parts in my code.
Additional to Risadinha's comment I share my current model structure here:
class AbstractDiscount(TimeStampedModel):
value = models.PositiveIntegerField(
verbose_name=_("Value"),
validators=[
MinValueValidator(0),
],
null=True,
blank=True,
)
percentage = models.DecimalField(
verbose_name=_("Percentage"),
max_digits=5,
decimal_places=4,
validators=[
MinValueValidator(0),
MaxValueValidator(1),
],
null=True,
blank=True,
)
type = models.CharField(
verbose_name=_("Type"),
max_length=10,
choices=TYPE_CHOICES,
)
redeemed_amount = models.PositiveIntegerField(
verbose_name=_("Amount of redeems"),
default=0,
)
class Meta:
abstract = True
class Discount(AbstractDiscount):
available_amount = models.PositiveIntegerField(
verbose_name=_("Available amount"),
)
valid_from = models.DateTimeField(
verbose_name=_("Valid from"),
help_text=_("Choose local time of event location. Leave empty and discount will be valid right away."),
null=True,
blank=True,
)
valid_until = models.DateTimeField(
verbose_name=_("Valid until"),
help_text=_("Choose local time of event location. Leave empty to keep discount valid till the event."),
null=True,
blank=True,
)
comment = models.TextField(
verbose_name=_("Comment"),
help_text=_("Leave some notes for this discount code."),
null=True,
blank=True,
)
status = models.CharField(
verbose_name=_("Status"),
max_length=12,
choices=STATUS_CHOICES,
default=STATUS_ACTIVE,
)
code = models.CharField(
verbose_name=_("Discount code"),
max_length=20,
)
event = models.ForeignKey(
Event,
related_name='discounts',
on_delete=models.CASCADE,
) # CASCADE = delete the discount if the event is deleted
tickets = models.ManyToManyField(
Ticket,
related_name='discounts',
blank=True,
help_text=_("Leave empty to apply this discount to all tickets"),
verbose_name=_("Tickets"),
)
class Meta:
verbose_name = _("Discount")
verbose_name_plural = _("Discounts")
ordering = ['code']
class SocialDiscount(AbstractDiscount):
event = models.OneToOneField(
Event,
related_name='social_ticketing_discount',
on_delete=models.CASCADE,
) # CASCADE = delete the discount if the event is deleted
tickets = models.ManyToManyField(
Ticket,
related_name='social_ticketing_discount',
blank=True,
help_text=_("Leave empty to apply this discount to all tickets"),
verbose_name=_("Tickets"),
)
class Meta:
verbose_name = _("SocialDiscount")
verbose_name_plural = _("SocialDiscount")
There is no generic answer to this, just considerations. The decision depends on the business logic you need to implement with this solution.
Two Columns
order.discount = ForeignKey(Discount, null=True)
order.social_discount = ForeignKey(SocialDiscount, null=True)
When checking in subsequent code:
if order.discount:
# do this based on Discount model
elif order.social_discount:
# do that based on SocialDiscount model
This is a solution in favor of two very different Discount behaviours.
Use this:
if there are only those two and no more in the future,
if you would call very different fields and methods on them (they have different business logic surrounding them).
Non-Abstract Parent
# renamed from AbstractDiscount to ParentDiscount for obvious reasons
order.discount = ForeignKey(ParentDiscount, null=True)
Subsequent code:
if order.discount:
# do things that apply to either discount
if isinstance(order.discount, 'Discount'):
# do things that only apply to Discount
elif isinstance(order.discount, 'SocialDiscount'):
# do things that only apply to SocialDiscount
Use this:
if there might be more children of ParentDiscount in the future,
if there is general business logic that applies to any type of ParentDiscount that would be shared between all children.
GenericForeignKey
Querying on GenericForeignKeys requires a bit of work. As #Andy remarked it is not directly supported, but you can of course query on content_type and object_id together. The __in lookup won't work unless you can rely on object_id only.
It won't work out of the box in forms. For the Django Admin, there might be some solution, though, see GenericForeignKey and Admin in Django.
Use this:
if there might be more discounts of various types in the future (ask the product owner and make sure this is not just some far far away future),
if there is general business logic that applies to any those types,
if you don't need a no-work quick Admin solution.

Django models, set unqiue attribute for M2M field per object

Currently, have a database with an Item table and a Stock table. There is a many to many relationship between the two. A single item object can have many sizes. The next step is to assign an 'inStock' options to the item per size.
Any thoughts on acheiving this?
Current models.py
class Stock(models.Model):
size = models.CharField(max_length=30)
stock = models.BooleanField(default=False)
def __str__(self):
return self.size
class Item(models.Model):
title = models.CharField(max_length=250, null=True, unique=True)
price = models.DecimalField(max_digits=8, decimal_places=2)
aw_product_id = models.CharField(max_length=11, null=True) # Removed because multiple products has similar identifer
url = models.URLField(max_length=250) # Removed 'unique=True'as the aw_prod_id will throw an integrity error
image = models.CharField(max_length=1000)
retailer = models.CharField(max_length=250)
category = models.CharField(max_length=100)
featured = models.CharField(max_length=10, default='NO')
gender = models.CharField(max_length=100, null=True)
sizes = models.ManyToManyField(Stock)
uniq_id = models.CharField(max_length=11, null=True, unique=True) # Removed because multiple products has similar identifer
def __str__(self):
return self.title
You can use the through argument to ManyToManyField to specify another model to use for the relationship, with additional fields instead of the autogenerated model that django creates by default.

Fetching model instance from a multiple direct relationship

Can anyone help me fetch data from this model structure? because i have a hard time doin this for hours now.
First I would like to get all distinct SubSpecialization from all Doctor which has a given Specialization.title
Secondly I would like to get all Doctor which has a specific Specialization.title and has no SubSpecialization.
Here is the Doctor model
class Doctor(models.Model):
name = models.CharField(max_length=50)
room_no = models.IntegerField()
floor_no = models.IntegerField()
contact_no = models.CharField(max_length=50, blank=True, null=True)
notes = models.CharField(max_length=70, blank=True, null=True)
This is the model Doctor relationship is connected to Specializationand SubSpecialization.
class DoctorSpecialization(models.Model):
doc = models.ForeignKey(Doctor, models.DO_NOTHING)
spec = models.ForeignKey('Specialization', models.DO_NOTHING)
class DoctorSubSpecialization(models.Model):
doc = models.ForeignKey(Doctor, models.DO_NOTHING)
sub_spec = models.ForeignKey('SubSpecialization', models.DO_NOTHING)
This is where i would make a criteria.
class Specialization(models.Model):
title = models.CharField(unique=True, max_length=45)
point = models.IntegerField()
class SubSpecialization(models.Model):
title = models.CharField(max_length=100)
There is no direct relationship between the Specialization and SubSpecialization please help.
Firstly, your specialization and subspecialization are both many-to-many relationships with Doctor. You should declare that explicitly, and drop those intervening models unless you need to store other information on them.
class Doctor(models.Model):
...
specializations = models.ManyToManyField('Specialization')
subspecializations = models.ManyToManyField('SubSpecialization')
Now you can query for all the subspecializations for doctors who have a specific specialization:
SubSpecialization.objects.filter(doctor__specialization__title='My Specialization')
Your second query doesn't make sense given the fact there is no relationship between specialization and subspecialization, you'll need to clarify what you mean by "no subspecialization in a specific specialization".
Edit
To find doctors who have a specific Specialization and then no subspecializations at all:
Doctor.objects.filter(specialization__name="My Specialization",
subspecialization=None)

Categories