I want to create an Abstract Base Class that other models inherit to track some basic information (Created_Date, Mod_date, Created_by, Mod_by, etc) here is an example of what I would like:
class MetaDataModel(models.Model):
added_by = models.ForeignKey(settings.AUTH_USER_MODEL, )
modified_by = models.ForeignKey(settings.AUTH_USER_MODEL)
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
active = models.BooleanField(default=True)
class Meta:
abstract = True
However, when I use this class in more than one other model I receive an error telling me:
takeoffs.Takeoff.added_by: (fields.E304) Reverse accessor for
'Takeoff.added_by' clashes with reverse accessor for
'InputTakeoffItem.added_by'.
HINT: Add or change a related_name argument to the definition for 'Takeoff.added_by' or 'InputTakeoffItem.added_by'.
across all models it's implemented in.
How can I create a ABC to track both who created and last modified an Item?
Sorry after struggling with this for a couple days, I actually found the answer in Django's documentation. There are two variables(?) you can add to the related_name field:
%(app_label)s
%(class)s
This will make the related_name unique to each class inheriting from it.
Documentation here: https://docs.djangoproject.com/en/1.10/topics/db/models/#abstract-related-name
Related
I have the following models:
from django.db import models
from foo.bar.models import Location
class AbstractShop(models.Model):
location = models.ForeignKey(Location, on_delete=models.CASCADE, related_name="%(class)s")
class Meta(self):
abstract = True
class Bakery(AbstractShop):
some_field = models.BooleanField("Some field", default=True)
class Meta:
verbose_name = "Bakery"
verbose_name_plural = "Bakeries"
class Supermarket(AbstractShop):
some_other_field = models.CharField("Some other field", max_length=20)
class Meta:
verbose_name = "Supermarket"
verbose_name_plural = "Supermarkets"
Now, Supermarket as well as Bakery inherit the location-ForeignKey from AbstractShop.
If I want to query the reverse relation to Bakery on the Location model, I would have to use bakerys (instead of the correct bakeries) as related_name - which I don't want as it's grammatically wrong and unintuitive.
So my questions are:
Is there any way to use the verbose_name_plural as related_name?
Is there any way at all to use any other related_name than "%(class)s" or "%(app_label)s or do I just have to implement the ForeignKey on the child classes?
If so, that would be somewhat annoying. Imagine you have a lot of shared ForeignKeys: You can move the ones with regular plurals in English to the abstract base class (ABC) (because for regular nouns the added "s" in "%(class)s" results in the correct plural form) while those with irregular plurals have to be implemented on the child class (as only there the related_name can be set to the plural of the actual name of the child class, which you don't know in the ABC).
This is a totally arbitrary condition which might not be obvious to non-native English speakers, also it transfers linguistic logic to code, which IMHO shouldn't happen.
class Product( models.Model ):
name = models.CharField(verbose_name="Name", max_length=255, null=True, blank=True)
the_products_inside_combo = models.ManyToManyField('self', verbose_name="Products Inside Combo", help_text="Only for Combo Products", blank=True)
However, I got this error when I tried to put the duplicate values:
From_product-to_product relationship with this From product and To
product already exists.
Screencap of the error.
Each pair (Product, Product) must be unique. This is why you get already exists error.
Behind the scenes, Django creates an intermediary join table to
represent the many-to-many relationship.
What do you want to do is to have many-to-many relationship between two models (nevermind that they are the same) with additional information stored - quantity (so you would have ProductA = 2x ProductB + ....
In order to model this relationship you will have to create intermediary model and use through option. Documentation explains it very well, so have a look:
https://docs.djangoproject.com/en/dev/topics/db/models/#intermediary-manytomany
Update
Here is minimal working example:
class Product(models.Model):
name = models.CharField(verbose_name='Name', max_length=255, null=True, blank=True)
products = models.ManyToManyField('self', through='ProductGroup', symmetrical=False)
def __str__(self):
return self.name
class ProductGroup(models.Model):
parent = models.ForeignKey('Product', related_name='+')
child = models.ForeignKey('Product', related_name='+')
and admin models:
class ProductGroupInline(admin.TabularInline):
model = Product.products.through
fk_name = 'parent'
class ProductAdmin(admin.ModelAdmin):
inlines = [
ProductGroupInline,
]
admin.site.register(Product, ProductAdmin)
admin.site.register(ProductGroup)
As you can see recursive Product-Product relation is modeled with ProductGroup (through parameter). Couple of notes:
Many-to-many fields with intermediate tables must not be symmetrical, hence symmetrical=False. Details.
Reverse accessors for ProductGroup are disabled ('+') (in general you can just rename them, however, you don't want to work with ProductGroup directly). Otherwise we would get Reverse accessor for 'ProductGroup.child' clashes with reverse accessor for 'ProductGroup.parent'..
In order to have a nice display of ManyToMany in admin we have to use inline models (ProductGroupInline). Read about them in documentation. Please note, however, fk_name field. We have to specify this because ProductGroup itself is ambiguous - both fields are foreign keys to the same model.
Be cautious with recurrency. If you would define, for example, __str__ on Product as: return self.products having ProductGroup with the same parent as the child you would loop infinitely.
As you can see in the screencap pairs can be duplicated now. Alternatively you would just add quantity field to ProductGroup and check for duplication when creating objects.
I have a task class, that can have sub-tasks, so it's a cyclic relationship. I'm passing it through a linker model/table like so:
class Task(models.Model):
title = models.CharField(max_length=100)
description = models.TextField(blank=True)
completed = models.BooleanField(default=False)
project = models.ForeignKey('Project', related_name="tasks")
dependancy = models.ManyToManyField('self', through='Dependancy', null=True,
blank=True, through_fields=('task', 'sub_task'), symmetrical=False)
def __str__(self):
return self.title
class Dependancy(models.Model):
task = models.ForeignKey(Task)
sub_task = models.ForeignKey(Task)
But I get this error:
ERRORS:
gantt_charts.dependency.sub_task: (fields.E303) Reverse query name for 'dependency.sub_task' clashes with field name 'Task.dependency'.
HINT: Rename field 'Task.dependency', or add/change a related_name argument to the definition for field 'dependency.sub_task'.
gantt_charts.dependency.sub_task: (fields.E304) Reverse accessor for 'dependency.sub_task' clashes with reverse accessor for 'dependency.task'.
HINT: Add or change a related_name argument to the definition for 'dependency.sub_task' or 'dependency.task'.
gantt_charts.dependency.task: (fields.E303) Reverse query name for 'dependency.task' clashes with field name 'Task.dependency'.
HINT: Rename field 'Task.dependency', or add/change a related_name argument to the definition for field 'dependency.task'.
gantt_charts.dependency.task: (fields.E304) Reverse accessor for 'dependency.task' clashes with reverse accessor for 'dependency.sub_task'.
HINT: Add or change a related_name argument to the definition for 'dependency.task' or 'dependency.sub_task'.
Obviously I need to set the related name on the Dependency.sub_task and Dependency.task field, and following the solution here is to name them something like task_task and task_sub_task, but that sounds wrong, unintuitive and confusing.
What would be a clear and concise name for them? It'd be easier if I wasn't getting confused over what related_names were, when using a linking table.
Given a Task instance, how can you access all the Dependencies that have that as their task? Or their sub_task? That's the purpose of related_nameāit provides the name for the attribute that Django will create on Task to point to that group of things.
You're seeing that error because Django automatically uses the name <model>_set, and since you have two ForeignKeys pointing to the same model, the default name will conflict.
Now, it might be that you never need to directly access Dependencies that way. If that's the case you can add related_name='+' to both fields and the reverse attribute won't be created at all (and your errors will disappear).
If you do want to access them, the name is up to you. I prefer longer-but-more-descriptive names to make their purpose clear. I might model the problem like this:
class Task(models.Model):
subtasks = models.ManyToManyField('self',
through='Dependancy',
symmetrical=False,
through_fields=('supertask', 'subtask'),
related_name='supertasks')
class Dependancy(models.Model):
supertask = models.ForeignKey(Task, related_name='dependencies_as_supertask')
subtask = models.ForeignKey(Task, related_name='dependencies_as_subtask')
class Meta:
unique_together = ('supertask', 'subtask')
>>> task = Task.objects.get()
>>> # all the Tasks that are supertasks of this one
>>> task.supertasks
>>> # all the Tasks that are subtasks of this one
>>> task.subtasks
>>> # all the Dependencies with this Task as the supertask
>>> task.dependencies_as_supertask
>>> # all the Dependencies with this Task as the subtask
>>> task.dependencies_as_subtask
What is the optimal way to implement standard fields that I would like all classes in Django models to have?
Option 1
Option 1 is to create the standard fields in a parent class and then have every class inherit that class. For instance:
Option 2
Option 2 is to create a class with the standard fields that has a composite relationship to the other classes. For instance:
Option 3
Option 3 is simply to duplicate these fields in each of the classes that wishes to use them (which violates the DRY principle of Django). For example:
Which is optimal for a notes field and created and last modified datetimes (and in the future also user id)? I clearly will wish to use these fields for auditing purposes, which means they will be displayed in Django Admin in some way. Keep this in mind when helping me determine which approach is optimal.
Your question suggests that you want to build an abstract class to use as a base class for your models to inherit from. There is some really great documentation here.
For example:
class CommonInfo(models.Model):
notes = models.TextField(blank=True, null=True)
created_at = models.DateTimeField(blank=True, default=datetime.datetime.now)
last_modified_at = models.DateTimeField(blank=True, default=datetime.datetime.now)
class Meta:
abstract = True # This line is what makes the class an abstract class
def we_all_do_this(self):
pass
def we_all_do_this_too(self):
pass
class ChildA(CommonInfo): # Note that ChildA class inherits from the CommonInfo abstract base class
name = models.CharField(blank=True, null=True, max_length=100)
state = models.CharField(blank=True, null=True, max_length=100)
def only_i_do_this(self):
pass
class ChildB(CommonInfo): # Note that ChildB class inherits from the CommonInfo abstract base class
title = models.CharField(blank=True, null=True, max_length=100)
date = models.DateField(default=datetime.datetime.today)
def only_i_do_this(self):
pass
I don't think there's a right answer to this question but if it is compulsory fields I expect to implement across all models in a project, I would go with the parent class approach.
i.e., your option 1.
Using the following code:
class Organization(models.Model):
name = models.CharField(max_length="100",)
alias = models.SlugField()
...
class Division(Organization):
parent_org = models.ForeignKey(Organization)
class Meta:
unique_together=['parent_org', 'alias']
...
Trying to syncdb give me this error:
Error: One or more models did not validate:
organizations.division: "unique_together" refers to alias. This is not in the
same model as the unique_together statement.
Any help is appreciated,
Thanks,
Eric
This is by design. Reading the documentation for the unique_together option, it states that:
It's used in the Django admin and is enforced at the database level.
If you look at the table that a subclass creates, you'll see that it doesn't actually have the fields that its parent has. Instead, it gets a soft Foreign Key to the parent table with a field name called [field]_ptr_id, where [field] is the name of the table you're inheriting from excluding the app name. So your division table has a Primary Foreign Key called organization_ptr_id.
Now because unique_together is enforced at the database level using the UNIQUE constraint, there's no way that I know of for the database to actually apply that to a field not in the table.
Your best bet is probably through using Validators at your business-logic level, or re-thinking your database schema to support the constraint.
Edit: As Manoj pointed out, you could also try using Model Validators such as validate_unique.
[Model] Validators would work for you. Perhaps simplest, though, would be to use:
class BaseOrganization(models.Model):
name = models.CharField(max_length="100",)
alias = models.SlugField()
class Meta:
abstract = True
class Organization(BaseOrganization):
pass
class Division(BaseOrganization):
parent_org = models.ForeignKey(Organization)
class Meta:
unique_together=['parent_org', 'alias']
Note: as with your current code, you could not have subdivisions of divisions.
This is a solution I recently used in Django 1.6 (thanks to Manoj Govindan for the idea):
class Organization(models.Model):
name = models.CharField(max_length="100",)
alias = models.SlugField()
...
class Division(Organization):
parent_org = models.ForeignKey(Organization)
# override Model.validate_unique
def validate_unique(self, exclude=None):
# these next 5 lines are directly from the Model.validate_unique source code
unique_checks, date_checks = self._get_unique_checks(exclude=exclude)
errors = self._perform_unique_checks(unique_checks)
date_errors = self._perform_date_checks(date_checks)
for k, v in date_errors.items():
errors.setdefault(k, []).extend(v)
# here I get a list of all pairs of parent_org, alias from the database (returned
# as a list of tuples) & check for a match, in which case you add a non-field
# error to the error list
pairs = Division.objects.exclude(pk=self.pk).values_list('parent_org', 'alias')
if (self.parent_org, self.alias) in pairs:
errors.setdefault(NON_FIELD_ERRORS, []).append('parent_org and alias must be unique')
# finally you raise the ValidationError that includes all validation errors,
# including your new unique constraint
if errors:
raise ValidationError(errors)
This does not strictly apply the question but is very closely related; unique_together will work if the base class is abstract. You can mark abstract model classes as such using:
class Meta():
abstract = True
This will prevent django from creating a table for the class, and its fields will be directly included in any subclasses. In this situation, unique_together is possible because all fields are in the same table.
https://docs.djangoproject.com/en/1.5/topics/db/models/#abstract-base-classes
I had similar challenge when trying to apply unique_together to a permission group created by a given client.
class ClientGroup(Group):
client = models.ForeignKey(Client, on_delete=models.CASCADE)
is_active = models.BooleanField()
class Meta():
# looking at the db i found a field called group_ptr_id.
# adding group_ptr did solve the problem.
unique_together = ('group_ptr', 'client', 'is_active')