In my back-end (API) site, there are certain fields that are common to most models, but not all. I have been using mixins for those fields so that I can include them for the models they apply to, and omit them from the ones they don't. For example:
class AddressPhoneModelMixin(models.Model):
address = models.TextField(
verbose_name=_('Address'),
blank=True,
null=True,
)
country = models.ForeignKey(
Country,
on_delete=models.SET_NULL,
verbose_name=_('Country'),
blank=True,
null=True,
)
phone_number = PhoneNumberField(
verbose_name=_('Phone'),
blank=True,
null=True,
)
mobile_number = PhoneNumberField(
verbose_name=_('Mobile Phone'),
blank=True,
null=True,
)
fax_number = PhoneNumberField(
verbose_name=_('Fax'),
blank=True,
null=True,
)
class Meta:
abstract = True
But I have other such mixins, and when a model needs to include all fields, the model definition gets to be quite long:
class Client(AddressPhoneModelMixin, DateFieldsModelMixin, models.Model):
And I now have other "common" fields I want to add, so it's only going to get worse. I want to keep all these common fields in one place for DRY, but also in case anything changes about a field, I only have one place to make the changes.
My idea is to have one mixin called CommonFieldsModelMixin, so that I will have only one mixin to include in the model definition. But for those models that don't need certain fields, the field definitions would all be wrapped in conditionals. Taking the above mixin as an example, and adding a conditional "Email Address" field, this is what I want to do:
class CommonFieldsModelMixin(models.Model):
address = models.TextField(
verbose_name=_('Address'),
blank=True,
null=True,
)
country = models.ForeignKey(
Country,
on_delete=models.SET_NULL,
verbose_name=_('Country'),
blank=True,
null=True,
)
if include_email: # <--this is what I want to add
email = models.EmailField(
verbose_name=_('Email Address'),
blank=True,
)
phone_number = PhoneNumberField(
verbose_name=_('Phone'),
blank=True,
null=True,
)
mobile_number = PhoneNumberField(
verbose_name=_('Mobile Phone'),
blank=True,
null=True,
)
fax_number = PhoneNumberField(
verbose_name=_('Fax'),
blank=True,
null=True,
)
class Meta:
abstract = True
Then when using the mixin on a model, it would be something like this:
class Client(CommonFieldsModelMixin, models.Model):
include_email = True
name = models.CharField(
verbose_name=_('Client'),
max_length=100,
)
status = models.CharField(
verbose_name=_('Status'),
max_length=25,
)
Notice the include_email = True property. In reality, all fields would be wrapped in conditionals, but this is intended as a simple example.
My question is, how can I access the include_email property of the parent from within the mixin? There isn't a self to use. I've also tried using super(), but that didn't work either. Is there any way to accomplish this?
And I'll also need to do the same (or similar) thing for the serializers. So if that would work differently, any suggestions there would be appreciated.
Related
I want to implement in django a model mixin. I have in
mixin.py:
class RightsModelRelation(models.Model):
user_link = models.ForeignKey(User, on_delete=models.CASCADE, blank=False, null=False, default=None,
related_name="%(class)s_rights_user")
right_to_view = models.BooleanField(default=True)
right_to_change = models.BooleanField(default=False)
right_to_delete = models.BooleanField(default=False)
class RightsModelMixin(models.Model):
rights_link = models.ManyToManyField(RightsModelRelation, default=None,
related_name="%(class)s_rights_link")
models.py:
class Address( RightsModelMixin,models.Model):
lastname = models.CharField(max_length=255, default="", )
firstname = models.CharField(max_length=255, default="", blank=True, null=True)
But this doesn't work. Can anyone help me implement a manytomany models mixin?
To achieve what you want you should create an abstract model. It's table will never be created you can use it just like python base class - so it matches your case. More on that in the docs
I have this model -
class News(BaseEntityBasicAbstract, HitCountMixin):
"""
News added from the dashboard with content
"""
NEWS_STATUS = (
('draft', _('Draft')),
('pending', _('Pending')),
('review', _('Review')),
('public', _('Public')),
('private', _('Private'))
)
backup = models.BooleanField(default=False)
prev_id = models.BigIntegerField(null=True, blank=True)
language = models.CharField(max_length=10, choices=LANGUAGES, default='bn', db_index=True)
heading = models.CharField(max_length=255, null=True, blank=True,
verbose_name=_('News Heading'),
help_text=_('Provide a news heading/caption.'))
sub_caption = models.TextField(max_length=255, null=True, blank=True,
verbose_name=_('Summary'),
help_text=_('Provide summary of the news.'))
url = models.CharField(max_length=255, unique=True, verbose_name=_('URL/Slug/Link'),
help_text=_('Unique url for the news without whitspace.'))
content = HTMLField(null=True, blank=True, verbose_name=_('Content'),
help_text=_('HTML content with texts, links & images.'))
featured_image = models.FileField(upload_to=FilePrefix('news/'), null=True, blank=True,
verbose_name=_('Featured Image'),
help_text=_('Upload a featured image for news.'))
image_caption = models.TextField(max_length=255, null=True, blank=True,
verbose_name=_('Image Caption'),
help_text=_('Provide a image caption.'))
status = models.CharField(max_length=20, choices=NEWS_STATUS, default='pending',
verbose_name=_('News Status'), db_index=True,
help_text=_('Only public news can be seen on front end.'))
source = models.ForeignKey(NewsSource, on_delete=models.SET_NULL, null=True, blank=True,
verbose_name=_('News Source'),
help_text=_('Select a news source.'))
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, blank=True,
verbose_name=_('Category'),
help_text=_('Select a news category.'))
tags = tagulous.models.TagField(
blank=True,
to=Tags,
verbose_name=_('News Tags'),
help_text=_('Provide news tags separated with commas.')
)
published_at = models.DateTimeField(null=True, blank=True, db_index=True,
verbose_name=_('Published At'))
menu_items = GenericRelation(MenuItems, object_id_field='id',
related_query_name='news_as_menu')
hit_count_generic = GenericRelation(HitCount, object_id_field='object_pk',
related_query_name='news_hit_count')
created_by = models.ForeignKey(User, related_name='news_created_by',
on_delete=models.SET_NULL, null=True, blank=True,
verbose_name=_('Created By'))
updated_by = models.ForeignKey(User, related_name='news_updated_by',
on_delete=models.SET_NULL, null=True, blank=True,
verbose_name=_('Last Updated By'))
published_by = models.ForeignKey(User, related_name='news_published_by',
on_delete=models.SET_NULL, null=True, blank=True,
verbose_name=_('Published By'))
deleted_by = models.ForeignKey(User, related_name='news_deleted_by',
on_delete=models.SET_NULL, null=True, blank=True,
verbose_name=_('Deleted By'))
Below is the serializer -
class NewsSerializer(serializers.ModelSerializer):
class Meta:
model = News
fields = ['id', 'heading', 'sub_caption', 'url', 'content', 'featured_image',
'image_caption', 'category', 'source', 'tags', 'published_at']
This is the view -
class NewsViewSets(viewsets.ModelViewSet):
queryset = News.objects.filter(
is_active=True,
status='public'
)
serializer_class = NewsSerializer
def get_queryset(self):
queryset = self.queryset.filter(
language=self.request.LANGUAGE_CODE
).order_by('-id')
return queryset
Pagination is set to only 10 in the settings.py, when I hit the news api url it takes 9/10 seconds to load only 10 records. Here's a screenshot showing django-debug-toolbar reports -
I have around 400k records on the database table, it may be an issue, but I think this is too much loading time. Please help me find the problem here! Thanks in advance.
Filtering can often be slow. It looks like you have database indexes on the relevant fields, but take note that if you have multiple filters, only one of the indexes will be used.
I'm guessing based on your columns but it seems like the most common query will always be looking for is_active=1 and status='public'. If this isn't the case you might have to make some tweaks.
Firstly, get rid of the db_index=True on the status, is_active, and language fields, otherwise your database writes will be slowed unnecessarily.
Then you can formulate an index such as this:
class Meta:
indexes = [
models.Index(
fields=["is_active", "status", "language"],
name="idx_filtering",
)
]
This will help the database when you filter on all three columns at once. If you're ever filtering on only one of these columns however, you may want to keep the original db_index=True.
If you were using PostgreSQL, you could do one better:
class Meta:
indexes = [
models.Index(
fields=["is_active", "status", "language"],
name="idx_filtering",
condition=Q(is_active=True, status="public"),
)
This would reduce the size of the index to only those matching the Q(), making traversing it faster.
One other thing to note is that pagination using OFFSET is very slow once you get to higher offsets. If at all possible you should be using DRF's cursor pagination instead.
I am trying to create a one to one reference and want to make sure that that reference is not allowed to be used for another model or instance.
For example
Say I have an address model, Person Model and Company Model
Person has a OneToOneField field to Address
Company also has a OneToOneField field to Address
address=Address(data="some address")
company=Company(name="some company",address=address)
person=Person(name="my name",address=address)
Models:
class Address(models.Model):
data = models.CharField(max_length=255, blank=True, null=True)
class Company(models.Model):
name = models.CharField(max_length=255, blank=True, null=True)
address=models.OneToOneField(Address,on_delete=models.CASCADE)
class Person(models.Model):
name = models.CharField(max_length=255, blank=True, null=True)
address=models.OneToOneField(Address,on_delete=models.CASCADE)
I would like the system to throw an error on this since I am setting the same address to 2 different models.
Also this would delete both person and company if I delete address.
Usually you catch this with code and not make a stupid mistake like this.
But can system catch it since it is one to one ?
In the case of the deletion you could use on_delete=models.PROTECT. In the other case you could add unique=True so a person id = 1 will have a address id = 1, a person id = 2 can't have a address id = 1 anymore. But it would only solve for one model:
address=models.ForeignKey(Address, unique=True, on_delete=models.PROTECT)
A new approach would be create a model to reference the address of both company and person and be able to forbid the creation with the same address id:
class AddressExample(models.Model):
id_address = models.ForeignKey(Address, unique=True,on_delete=models.PROTECT)
id_person = models.ForeignKey(Person, blank=True, null=True, unique=True, on_delete=models.PROTECT)
id_company = models.ForeignKey(Person, blank=True, null=True, unique=True, on_delete=models.PROTECT)
Note that I used blank=True, null=True so you can create an instance only with a Person or a Company, without the need to create a instance with both. There is a Meta to use combination of primary keys too.
class AddressExample(models.Model):
id_address = models.ForeignKey(Address, unique=True,on_delete=models.PROTECT)
id_person = models.ForeignKey(Person, blank=True, null=True, unique=True, on_delete=models.PROTECT)
id_company = models.ForeignKey(Person, blank=True, null=True, unique=True, on_delete=models.PROTECT)
class Meta:
unique_togther = ('id_address', 'id_person', 'id_company')
# Not sure if it will throw a error here because `id_person` and `id_company` can be blank
# or null. But the use of `unique together` is for cases where you want to guarantee
# the combination of the primary keys will be unique.
Hope it helps.
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.
I have a get_or_create() in my django app that's creating duplicate rows and assigning them the same id.
stock_search, created = SearchRequest.objects.get_or_create(quote=quote, salesperson=user)
count() doesn't count these rows more than once but any queries I run on the data returns the duplicated rows.
Any ideas what could be causing this to happen?
Model Definition
class SearchRequest(models.Model):
salesperson = models.ForeignKey(User, blank=True, null=True, related_name='sales')
purchaser = models.ManyToManyField(User, blank=True, null=True, related_name='purchaser')
datesent = models.DateTimeField(auto_now_add=False, verbose_name=("Date Sent"), blank=True, null=True)
notes = models.TextField(default='', blank=True, null=True)
full_search = models.BooleanField(verbose_name=("Full Search"), blank=True, default=False)
quote = models.ForeignKey('Quote.Quote', blank=True, null=True)
lead_time = models.ForeignKey('Logistics.LeadTime', blank=True, null=True)
call_list = models.BooleanField(verbose_name=("Call List"), blank=True, default=False)
email_list = models.BooleanField(verbose_name=("Email List"), blank=True, default=False)
accepted = models.ForeignKey(User, blank=True, null=True, related_name='search_accepted')
dateaccepted = models.DateTimeField(auto_now_add=False, verbose_name=("Date Accepted"), blank=True, null=True)
Cheers
As mentioned in the docs, you need an unique index for get_or_create to work
This method is atomic assuming correct usage, correct database
configuration, and correct behavior of the underlying database.
However, if uniqueness is not enforced at the database level for the
kwargs used in a get_or_create call (see unique or unique_together),
this method is prone to a race-condition which can result in multiple
rows with the same parameters being inserted simultaneously.
So you class needs
class SearchRequest(models.Model):
class Meta:
unique_together('quote','salesperson')
which should be placed after the field definitions.