Recently I've faced an issue with Django, model inheritance and how the creation of model instances works.
Suppose I have the following (basic) setup:
class InviteBaseManager(models.Manager):
def create(self):
new_code = # create some kind of unique code, not really relevant here.
return super().create(code=new_code)
class InviteBase(models.Model):
code = models.CharField(max_length=10, blank=False, null=False, unique=True)
creationDate = models.DateTimeField(default=timezone.now())
objects = InviteBaseManager()
class PartyInviteManager(models.Manager):
def create(self, name):
# method 1
newInvite = InviteBase.objects.create()
print(newInvite.code) # is definitly set, lets assume "ABCD"
# as expexted, the "InviteBase" table has one row with code "ABCD" and
# default creationDate
newPartyInvite = super().create(partyName=name, invite=newInvite)
print(newPartyInvite.invite.code) # is EMPTY, prints nothing
# In fact, when looking at the db, there is still only *one* row in the table "InviteBase",
# with an *empty* code field and a default creationDate field.
return newPartyInvite
#method 2
newPartyInvite = super().create(partyName=name)
# creates the InviteBase instance implicitly, again, newPartyInvite.invite.code is empty.
# fill newPartyInvite.invity.code manually.
return newPartyInvite
class PartyInvite(InviteBase):
#Isn't blank=False and null=False unnecessary? A child should probably not exist with no parent?
invite = models.OneToOneField(InviteBase, parent_link=True, on_delete=models.CASCADE, null=False, blank=False)
partyName = models.CharField(...)
objects = PartyInviteManager()
So the question is: How can I pass an already existing instance of the base class inside the create method of my PartyInviteManager? Even when using method 1, the existing instance that I pass along seems to be overwritten. A new one is created with the default value. Interestingly enough, this violates the constraints that code cannot be blank or NULL.
This behaviour seems a bit odd to me? Can someone point out what I am missing here?
To clarify: I know that I should usually use **kwargs in the create methods and that inheritance might not be the ideal use case here, but I'm just very curious about this behaviour.
I know that this kind of model inheritance wont even create a pk for the child model (because holding a OneToOneField to the parent class effectively acts as a pk anyway), but why would it be impossible to pass a manually created instance as parent? Am I not allowed to use inheritance for my use-case?
I think I've found the correct way of doing this:
class InviteBaseManager(models.Manager):
def create(self, **kwargs):
kwargs['code'] = #createCode
return super().create(**kwargs)
class InviteBase(models.Model):
code = models.CharField(max_length=10, blank=False, null=False, unique=True)
creationDate = models.DateTimeField(default=timezone.now())
objects = InviteBaseManager()
class PartyInviteManager(InviteBaseManager):
def create(self, name):
return = super().create(partyName=name)
class PartyInvite(InviteBase):
invite = models.OneToOneField(InviteBase, parent_link=True, on_delete=models.CASCADE, null=False, blank=False)
partyName = models.CharField(...)
objects = PartyInviteManager()
The PartyInviteManager now inherits from the InviteManager as well. When calling super.create() from the child manager, the base manager gets called, appends the code field and everything works as expected.
I also found out, that if the PartyInviteManager does not inherit from the InviteBaseManager, the create method of InviteBaseManager does not get called when creating a new PartyInvite. This seems very odd to me.
Of course, the easier way would have been to create the code as default value via a function, like that:
def createCode():
return "ABCD" # add fancy code creation magic here.
class InviteBase:
code = models.CharField(max_length=128, blank=False, null=False, default=createCode)
But if, depending on the child class, the code needs additional information (such as invited members or whatever), this approach would not work anymore.
On a side note, interestingly enough the following code snippet:
invite = PartyInviteManager.objects.create(partyName='Birthday')
print(invite.invite.code)
print(invite.code)
produces the following output:
ABCD
ABCD
In the create method of the PartyInviteManager, one can directly use code=XXXX to pass along a string for the InviteBase model, invite_code on the other hand does not work.
Related
I am trying to build a filter by function in django. From what I've learned by googling this is quite hard to achieve.
So here is my code:
class TrackingEventType(models.Model):
name = models.CharField(blank=False, null=False, max_length=255)
class TrackingEvent(models.Model):
datetime = models.DateTimeField(blank=False, null=False, default=datetime.now, verbose_name="Zeitpunkt")
event_type = models.ForeignKey(TrackingEventType, help_text="Art des Events")
tracking = models.ForeignKey('Tracking')
class Meta:
ordering = ['-datetime']
class Tracking(models.Model):
tracking_no = models.CharField(blank=False, null=False, max_length=10, unique=True, verbose_name="Tracking Nummer")
def get_last_event(self):
"""
Todo: return the latest event.
"""
return TrackingEvent.objects.filter(tracking=self.id).first()
get_last_event.short_description = 'Last event'
class Meta:
ordering = ['-tracking_no']
My goal is to make it possible to filter Tracking objects by their last events type name. Displaying the result of the funtion in django admin is easy, but adding a corresponding filter isn't.
My idea was also to try to build a filter something like:
trackingevent__set__first__event_type__name
But yeah, that would be too easy :)
Any inputs are welcome.
As you've discovered it isn't trivial to filter in that manner. If you are accessing that information regularly it is probably also not very efficient either.
I would suggest that you store a reference to the latest tracking event in the Tracking model itself:
class Tracking(models.Model)
# ...
last_event = models.ForeignKey(TrackingEvent, null=True)
You would then use signals to update this reference whenever a new tracking event is created. Something along the lines of:
from django.db.models.signals import post_save
from django.dispatch import receiver
#receiver(post_save, sender=TrackingEvent)
def update_latest_tracking_event(sender, instance, created, **kwargs):
# Is this a new event?
if created:
# If yes, then update the Tracking reference
tracking = instance.tracking
tracking.last_event = instance
tracking.save()
(Please read the documentation on where to put this code).
Once all this is in place it becomes easy to filter based on the last tracking event type:
# I'm just guess what event types you have...
cancellation = TrackingEventType.objects.get(name='cancel')
Tracking.objects.filter(last_event__event_type=cancellation)
I'd like to create a many-to-many relationship from and to a user class object.
I have something like this:
class MyUser(models.Model):
...
blocked_users = models.ManyToManyField(MyUser, blank=True, null=True)
The question is if I can use the class reference inside itself. Or do I have to use "self" insead of "MyUser" in the ManyToManyField? Or is there another (and better) way to do it?
Technically, I'm pretty sure "MyUser" or "self" will work, as long as it's a string in either case. You just can't pass MyUser, the actual class.
However, the docs always use "self". Using "self" is not only more explicit about what's actually happening, but it's impervious to class name changes. For example, if you later changed MyUser to SomethingElse, you would then need to update any reference to "MyUser" as well. The problem is that since it's a string, your IDE will not alert you to the error, so there's a greater chance of your missing it. Using "self" will work no matter what the class' name is now or in the future.
class MyUser(models.Model):
...
blocked_users = models.ManyToManyField("self", blank=True)
Don't forget use symmetrical=False, if you use .clear() or .add() method for related objects and don't wanna object on other side of relation update own data in relation field.
some_field = models.ManyToManyField('self', symmetrical=False)
I think it should be class name instead of self. because with using self like this
parent = models.ManyToManyField('self', null=True, blank=True)
when i add parent:
user1.parent.add(user2)
i have 2 record in database like this:
and with using class name liken this:
parent = models.ManyToManyField('User', null=True, blank=True)
i have one record in database like this:
note that i use uuid for pk and i use django 3.1
EDIT:
as #shinra-tensei explained as comment in this answer we have to set symmetrical to False if we use self. documented in Django Documents: ManyToManyField.symmetrical
If you use self or MyUser you will get a NameError in both cases. You should write "self" as string. See the example below:
class MyUser(models.Model):
...
blocked_users = models.ManyToManyField("self", blank=True, null=True)
And do not forget to set the symmetrical attribute to False if the relationship is not symmetrical.
For further details check: https://docs.djangoproject.com/en/3.0/ref/models/fields/#django.db.models.ManyToManyField
don't use 'self' in ManyToManyField, it will cause you object link each other, when use django form to submit it
class Tag(models.Model):
...
subTag = models.ManyToManyField("self", blank=True)
...
aTagForm.save()
and result:
a.subTag == b
b.subTag == a
I am writing an application to help employees track projects the are working on.
Part of the form should allow logged in employees to click a drop down and see all projects they are working on. This is the part I am struggling with; specifically getting ONLY the logged in user's projects populated in a drop down. Any help or insight is much appreciated. Thanks……
models.py
class Photo(models.Model):
image = models.ImageField(upload_to='uploads/images/photo')
title = models.CharField(max_length=50)
def __unicode__(self):
return self.title
class Employee(models.Model):
user = models.ForeignKey(User, unique=True)
photo = models.ImageField(upload_to='uploads/images')
department = models.ForeignKey(Department, null=True)
phone = PhoneNumberField("Phone")
def __unicode__(self):
return self.user.get_full_name()
class Projects(models.Model):
name = models.CharField(max_length=40)
student = models.ForeignKey(Student)
photos = models.ManyToManyField(Photo, blank=True, null=True)
forms.py
class ProjectsForm(forms.ModelForm):
employee = get_object_or_404(Employee, user=user)
employee_projects = employee.projects_set.all()
name = forms.ModelChoiceField(queryset=employee_projects,
empty_label="(Select a Project)", required=True)
class Meta:
model = Projects
You need to put first two lines from ProjectsForm class definition to its initialization method and change them a bit.
class ProjectsForm(forms.ModelForm):
name = forms.ModelChoiceField(queryset=Employee.objects.all(),
empty_label="(Select a Project)", required=True)
class Meta:
model = Projects
def __init__(self, user, *args, **kwargs):
super(self, ProjectsForm).init(*args, **kwargs)
employee = get_object_or_404(Employee, user=user)
self.fields['name'].queryset = employee.projects_set.all()
Now, some explanation. Hope someone will find it useful.
In your original ProjectsForm definition, you're trying to get employee's projects when your class is defined. But this happens once your forms.py file is compiled, which takes place rarely (when you change code, for example). Also, you of course have no data that is necessary to filter projects, i.e., no user object, at that stage.
Instead, you need to do this each time the class is initialized. Initialization in Python takes place in special __init__() method. For example, when you're doing something like
form = ProjectsForm(data=request.POST)
in your view, what happens is that ProjectsForm.__init__(data=request.POST) is called to initialize ProjectsForm into an instance.
So, in our new definition, we're requiring a new argument (user) to be passed to the form when it's instantiated. So you can do something like this in your view:
form = ProjectsForm(request.user, data=request.POST)
In new initialization method, first we're calling the initialization method of parent class (which does some internal django things and should be called anyway), then use argument user to get related employee, and then assign a queryset with that employee's projects to the name field.
Sorry if I'm being too verbose.
See also:
Django, passing information when modifying queryset for ModelForm
django - dynamic select fields in forms
Why not have a many to many field in the Employee model that points to all the projects the employee can work on?
https://docs.djangoproject.com/en/dev/ref/models/fields/#django.db.models.ManyToManyField
I think that would be the best way to do it.
Google has lots of examples of doing reverse lookup in the interactive prompt but none of doing them inside a django model as a method.
I have the following models.py file:
class Client(models.Model):
...
def __unicode__(self):
return ???
class ClientDetails(models.Model):
client = models.ForeignKey(Client, null=True)
created = models.DateTimeField(default=datetime.now)
created_by = models.ForeignKey(User, null=True)
name_title = models.CharField(max_length=3, choices=NAME_TITLE_CHOICES)
first_name = models.CharField(max_length=40)
middle_name = models.CharField(max_length=40)
last_name = models.CharField(max_length=40)
...
How do I get the client method to return last_name from ClientDetails?
If a ClientDetails object should only ever be associated with a single Client object, then I would change your FK to a OneToOneField, which will provide you with a neat reverse accessor that can only ever link between a given Client and its associated ClientDetails. Then you can do:
try:
return self.clientdetails.last_name
except ClientDetails.DoesNotExist:
##handle it here, returning a graceful message
Alternatively, if you kept it as a FK, then you'd have to do something like:
try:
self.clientdetails_set.all()[0].last_name
except IndexError, e:
## handle exception here
but using a FK here is brittle and not great form (as the exception-handling implies: if there is none returned, then you'll get an IndexError. Also, there might be more than one ClientDetails object linked to that Client, and you'd only get the details of the first one here.)
So, I really would recommend using a OneToOneField instead of that FK. (All a OneToOneField is basically an FK with unique=True set on it and some neater accessors than standard FKs)
For that you can use in client the *clientdetails_set* to access all the ClientDetails objects that are linked to that client.
The set is an object set from Django, so calling the method all() will retrieve every of the objects. If you know there's only one you can do self.clientdetails_set.all()[0].last_name
Here's the link for the django documentation: Link
I'm looking to do this:
class Place(models.Model):
name = models.CharField(max_length=20)
rating = models.DecimalField()
class LongNamedRestaurant(Place): # Subclassing `Place`.
name = models.CharField(max_length=255) # Notice, I'm overriding `Place.name` to give it a longer length.
food_type = models.CharField(max_length=25)
This is the version I would like to use (although I'm open to any suggestion):
http://docs.djangoproject.com/en/dev/topics/db/models/#id7
Is this supported in Django? If not, is there a way to achieve similar results?
Updated answer: as people noted in comments, the original answer wasn't properly answering the question. Indeed, only the LongNamedRestaurant model was created in database, Place was not.
A solution is to create an abstract model representing a "Place", eg. AbstractPlace, and inherit from it:
class AbstractPlace(models.Model):
name = models.CharField(max_length=20)
rating = models.DecimalField()
class Meta:
abstract = True
class Place(AbstractPlace):
pass
class LongNamedRestaurant(AbstractPlace):
name = models.CharField(max_length=255)
food_type = models.CharField(max_length=25)
Please also read #Mark answer, he gives a great explanation why you can't change attributes inherited from a non-abstract class.
(Note this is only possible since Django 1.10: before Django 1.10, modifying an attribute inherited from an abstract class wasn't possible.)
Original answer
Since Django 1.10 it's
possible!
You just have to do what you asked for:
class Place(models.Model):
name = models.CharField(max_length=20)
rating = models.DecimalField()
class Meta:
abstract = True
class LongNamedRestaurant(Place): # Subclassing `Place`.
name = models.CharField(max_length=255) # Notice, I'm overriding `Place.name` to give it a longer length.
food_type = models.CharField(max_length=25)
No, it is not:
Field name “hiding” is not permitted
In normal Python class inheritance, it is permissible for a child
class to override any attribute from the parent class. In Django, this
is not permitted for attributes that are Field instances (at least,
not at the moment). If a base class has a field called author, you
cannot create another model field called author in any class that
inherits from that base class.
That is not possible unless abstract, and here is why: LongNamedRestaurant is also a Place, not only as a class but also in the database. The place-table contains an entry for every pure Place and for every LongNamedRestaurant. LongNamedRestaurant just creates an extra table with the food_type and a reference to the place table.
If you do Place.objects.all(), you also get every place that is a LongNamedRestaurant, and it will be an instance of Place (without the food_type). So Place.name and LongNamedRestaurant.name share the same database column, and must therefore be of the same type.
I think this makes sense for normal models: every restaurant is a place, and should have at least everything that place has. Maybe this consistency is also why it was not possible for abstract models before 1.10, although it would not give database problems there. As #lampslave remarks, it was made possible in 1.10. I would personally recommend care: if Sub.x overrides Super.x, make sure Sub.x is a subclass of Super.x, otherwise Sub cannot be used in place of Super.
Workarounds: You can create a custom user model (AUTH_USER_MODEL) which involves quite a bit of code duplication if you only need to change the email field. Alternatively you can leave email as it is and make sure it's required in all forms. This doesn't guarantee database integrity if other applications use it, and doesn't work the other way around (if you want to make username not required).
See https://stackoverflow.com/a/6379556/15690:
class BaseMessage(models.Model):
is_public = models.BooleanField(default=False)
# some more fields...
class Meta:
abstract = True
class Message(BaseMessage):
# some fields...
Message._meta.get_field('is_public').default = True
My solution is as simple as next monkey patching, notice how I changed max_length attribute of name field in LongNamedRestaurant model:
class Place(models.Model):
name = models.CharField(max_length=20)
class LongNamedRestaurant(Place):
food_type = models.CharField(max_length=25)
Place._meta.get_field('name').max_length = 255
Pasted your code into a fresh app, added app to INSTALLED_APPS and ran syncdb:
django.core.exceptions.FieldError: Local field 'name' in class 'LongNamedRestaurant' clashes with field of similar name from base class 'Place'
Looks like Django does not support that.
This supercool piece of code allows you to 'override' fields in abstract parent classes.
def AbstractClassWithoutFieldsNamed(cls, *excl):
"""
Removes unwanted fields from abstract base classes.
Usage::
>>> from oscar.apps.address.abstract_models import AbstractBillingAddress
>>> from koe.meta import AbstractClassWithoutFieldsNamed as without
>>> class BillingAddress(without(AbstractBillingAddress, 'phone_number')):
... pass
"""
if cls._meta.abstract:
remove_fields = [f for f in cls._meta.local_fields if f.name in excl]
for f in remove_fields:
cls._meta.local_fields.remove(f)
return cls
else:
raise Exception("Not an abstract model")
When the fields have been removed from the abstract parent class you are free to redefine them as you need.
This is not my own work. Original code from here: https://gist.github.com/specialunderwear/9d917ddacf3547b646ba
Maybe you could deal with contribute_to_class :
class LongNamedRestaurant(Place):
food_type = models.CharField(max_length=25)
def __init__(self, *args, **kwargs):
super(LongNamedRestaurant, self).__init__(*args, **kwargs)
name = models.CharField(max_length=255)
name.contribute_to_class(self, 'name')
Syncdb works fine. I dont tried this example, in my case I just override a constraint parameter so ... wait & see !
I know it's an old question, but i had a similar problem and found a workaround:
I had the following classes:
class CommonInfo(models.Model):
image = models.ImageField(blank=True, null=True, default="")
class Meta:
abstract = True
class Year(CommonInfo):
year = models.IntegerField()
But I wanted Year's inherited image-field to be required while keeping the image field of the superclass nullable. In the end I used ModelForms to enforce the image at the validation stage:
class YearForm(ModelForm):
class Meta:
model = Year
def clean(self):
if not self.cleaned_data['image'] or len(self.cleaned_data['image'])==0:
raise ValidationError("Please provide an image.")
return self.cleaned_data
admin.py:
class YearAdmin(admin.ModelAdmin):
form = YearForm
It seems this is only applicable for some situations (certainly where you need to enforce stricter rules on the subclass field).
Alternatively you can use the clean_<fieldname>() method instead of clean(), e.g. if a field town would be required to be filled in:
def clean_town(self):
town = self.cleaned_data["town"]
if not town or len(town) == 0:
raise forms.ValidationError("Please enter a town")
return town
You can not override Model fields, but its easily achieved by overriding/specifying clean() method. I had the issue with email field and wanted to make it unique on Model level and did it like this:
def clean(self):
"""
Make sure that email field is unique
"""
if MyUser.objects.filter(email=self.email):
raise ValidationError({'email': _('This email is already in use')})
The error message is then captured by Form field with name "email"