Trouble overriding save method on Django model with ManyToManyField - django

I'm having trouble overriding the save method on a Django model to check a restriction on a many-to-many field.
Say I have the following models:
class Person(models.Model):
name = models.CharField()
class ClothingItem(models.Model):
description = models.CharField()
owner = models.ForeignKey(Person)
class Outfit(models.Model):
name = models.CharField()
owner = models.ForeignKey(Person)
clothing_items = models.ManyToManyField(ClothingItem)
I would like to put a restriction on the save method of Outfit that ensures that each ClothingItem in a given outfit has the same owner as the Outfit itself.
I.e. I'd like to write:
class Outfit(models.Model):
name = models.CharField()
owner = models.ForeignKey(Person)
clothing_items = models.ManyToManyField(ClothingItem)
def save(self, *args, **kwargs):
for ci in self.clothing_items:
if ci.owner != self.owner:
raise ValueError('You can only put your own items in an outfit!)
super(Outfit, self).save(*args, **kwargs)
but when I try that I get an error about <Outfit: SundayBest>" needs to have a value for field "outfit" before this many-to-many relationship can be used.
Any ideas what's going wrong here?

There are two issues going on here. To directly answer your question, the error basically means: You cannot refer to any m2m relationship if the original object(an instance of Outfit here) is not saved in database.
Sounds like you are trying to do the validation in save() method, which is a pretty bad practice in django. The verification process should typically happen in Form that creates Outfit objects. To override default django form, please refer to django ModelAdmin.form. To understand how to do validation on django forms, check ModelForm validation.
If you want code to refer to for m2m validation, I found a good example from SO.

Related

Django models - how to track a field on an object when value of the field depends on the different users?

I want a Book object with the field is_read, but the value of the is_read depends on the user. When I first created this app I was only thinking of one user (me).
class Book(models.Model):
title = models.CharField(max_length=50)
author = models.CharField(max_length=50)
is_read = models.BooleanField(default=False)
But if the app has multiple users then of course the is_read needs to change according to the user.
new models
class Book(models.Model):
title = models.CharField(max_length=50)
author = models.CharField(max_length=50)
class IsRead(models.Model):
user = models.ForeignKey(User, on_delete=CASCADE)
book = models.ForeignKey(Book, on_delete=CASCADE)
is_read = models.BooleanField(default=False)
I think adding the IsRead class will help but I need an IsRead object to automatically be created every time a new user or book is created. Not only that, but every time a new user is created, I have to iterate through all the books. Or if a new book is added, I have to iterate through all the users. This seems like a lot of db work just to keep track of who has read what books.
Even if the above is the correct strategy I don't know how to I would do it. I did try to overwrite AdminModel to save the IsRead but this did not work. I did not get any errors, but the IsRead did not save.
class BookAdmin(admin.ModelAdmin):
list_display = ('title', 'author')
def save_model(self, request, obj, form, change):
obj.user = request.user
IsRead.objects.update_or_create(book=id, user=request.user, defaults={'book': self.book, 'user': obj.user, 'my_status': False})
super(BookAdmin, self).save_model(request, obj, form, change
Your proposed solution of using an IsRead class to track progress will be very expensive for your db not to mention there will be a lot of unnecessary rows saved. The creation for these models can be synced, however, using signals.
You can do it something like this:
#receiver(post_save, sender=Book)
def on_book_create(sender, instance, created, *args, **kwargs):
if not created or kwargs.get("raw"):
return
# fetch all users
# loop through users and create an IsRead object
and do a counterpart signal for on_create_user too where you fetch all books and create an IsRead object linked to that user.
A better way is to leverage the power of Many-to-Many relationship in django. Start by adding a field for this relationship
class Book(models.Model):
...
readers = models.ManyToManyField(User)
You can then add readers to a book instance like this:
# 1, 2, 3 are user pks
book.readers.add(1, 2, 3)
# or a user instance
book.readers.add(user)
After that, we can add custom functions to the User and Book models to give us if a book was read by a user or if a user has read a certain book.
class User(models.Model):
...
def has_read(self, book_id):
return self.book_set.filter(pk=book_id).exists()
class Book(models.Model):
...
def is_read(self, user_id):
return self.readers.filter(pk=user_id).exists()
To check, we can do it like so:
user.has_read(book.pk)
# or like this
book.is_read(user.pk)
And as a bonus, you can get the books read by the user user.book_set.all() as well as all readers of that book book.readers.all()
Lastly, for this:
I was thinking more of is_read is the status of the book being read. A
m2m relationship between Book and User would be more like the user
owns the book.
If you want to represent a user's ownership to a certain book you can add a different many-to-many field like owners or something. Just like in real life, your relationship to a book is not limited to your ownership with it.
Adding another many-to-many relationship using the same model requires the related_name parameter to be set though. This is to avoid confusing django which relationship you want to get.
class Book(models.Model):
...
readers = models.ManyToManyField(User, related_name="read_books")
owners = models.ManyToManyField(User, related_name="owned_books")
You can have query book->user relationship using the field name book.readers.all() or book.owners.all() but for the reverse, you need to reference the related name for each: user.read_books.all() or user.owned_books.all().
This is not really far from your original IsRead class as django creates a pivot table for book and user models behind the scenes which looks something like this:
And instead of having a dedicated is_read field to check for the read status, the existence of the record implies it that's why we use .exists()
Putting them all together to something like:
class User(models.Model):
name = models.CharField(max_length=50)
def has_read(self, book_id):
return self.read_books.filter(pk=book_id).exists()
def owns_book(self, book_id):
return self.owned_books.filter(pk=book_id).exists()
class Book(models.Model):
title = models.CharField(max_length=50)
author = models.CharField(max_length=50)
readers = models.ManyToManyField(User, related_name="read_books")
owners = models.ManyToManyField(User, related_name="owned_books")
def is_read(self, user_id):
return self.readers.filter(pk=user_id).exists()
def is_owned(self, user.id):
return self.owners.filter(pk=user.id).exists()

Django REST Framework nested relations without related names

I have some models that relates to User, but does not have a related name on user:
class Registration(models.Model):
user = models.OneToOneField('auth.User', related_name='+')
class ManyToOneModel(models.Model):
user = models.ForeignKey('auth.User', related_name='+')
I would like to make a serializer for User, which can have this as a nested resource. Is there a way to specify what the queryset/object is? This is an example of what I have - and it completly expectedly failes with 'User' object has no attribute 'registration':
class UserSerializer(serializers.Serializer):
pk = serializers.Field()
registration = RegistrationSerializer()
many_to_one_model = ManyToOneModelSerializer(many=True, required=False)
I guess you'd need to manually query for the related objects and then construct the serializers by hand. You'd then construct the final representation and pass that as the data parameter to a Response object.
It seems like you're making life difficult though. If you just define the related_name on your related models you could use ModelSerializer (or HyperlinkedModelSerializer) and it would all Just Work™. — Is there some reason why you can't do this?

Django model design: editable help text for individual model fields. Is there a foreign field that references a specific field of a model?

I have several models with several fields in my app. I want to set up a way for the user to be able to modify a help text system for each field in the model. Can you give me some guidance on how to design the models, and what field types to use? I don't feel right about storing the model and field name in CharFields, but if that is the only way, I may be stuck with it.
Is there a more elegant solution using Django?
For a quick and silly example, with an app named jobs, one named fun, and make a new app named helptext:
jobs.models.py:
class Person(models.Model):
first_name = models.CharField(max_length=32)
.
.
interests = models.TextField()
def __unicode__(self):
return self.name
class Job(models.Model):
name = models.CharField(max_length=128)
person = models.ForeignKey(Person)
address = models.TextField()
duties = models.TextField()
def __unicode__(self):
return self.name
fun.models.py:
class RollerCoaster(models.Model):
name = models.CharField(max_length=128)
scare_factor = models.PositiveInteger()
def __unicode__(self):
return self.name
class BigDipper(RollerCoaster):
max_elevation = models.PositiveInteger()
best_comment_ever_made = models.CharField(max_length=255)
def __unicode__(self):
return super.name
Now, let's say I want to have editable help text on Person.interests, and Job.duties, RollerCoaster.scare_factor, and BigDipper.best_comment_ever_made. I'd have something like:
helptext.models.py:
from django.contrib.contenttypes.models import ContentType
class HelpText(models.Model):
the_model = models.ForeignKey(ContentType)
the_field = models.CharField(max_length=255)
helptext = models.CharField(max_length=128)
def __unicode__(self):
return self.helptext
So, what is the better way to do this, other than making HelpText.the_model and HelpText.the_field CharFields that have to be compared when I am rendering the template to see if helptext is associated with each field on the screen?
Thanks in advance!
Edit:
I know about the help_text parameter of the fields, but I want this to be easily edited through the GUI, and it may contain a LOT of help with styling, etc. It would be HTML with probably upwards of 50-60 lines of text for probably 100 different model fields. I don't want to store it in the field definition for those reasons.
I changed the HelpText model to have a reference to ContentType and the field a CharField. Does this seem like a good solution? I am not sure this is the most elegant way. Please advise.
Edit 2013-04-19 16:53 PST:
Currently, I tried this and it works, but not sure this is great:
from django.db import models
from django.contrib.contenttypes.models import ContentType
# Field choices for the drop down.
FIELDS = ()
# For each ContentType verify the model_class() is not None and if not, add a tuple
# to FIELDS with the model name and field name displayed, but storing only the field
# name.
for ct in ContentType.objects.all():
m = ct.model_class()
if m is not None:
for f in ct.model_class()._meta.get_all_field_names():
FIELDS += ((f, str(ct.model) + '.' + str(f)),)
# HelpText model, associated with multiple models and fields.
class HelpText(models.Model):
the_model = models.ForeignKey(ContentType)
the_field = models.CharField(max_length=255, choices=FIELDS)
helptext = models.TextField(null=True, blank=True)
def __unicode__(self):
return self.helptext
Doesn't feel like the best, but please advise if this is a solution that will bite me in the behind later on and make me filled with regrets... :*(
The solution works, and I have it implemented, but you have to be aware that sometimes the ContentTypes get out of sync with your models. You can manually update the content types with this:
python manage.py shell
>>> from django.contrib.contenttypes.management import update_all_contenttypes
>>> update_all_contenttypes(interactive=True)
This allows you to add the new ones and remove the old ones, if they exist.
The nice thing about the Field not being a foreign key is that I can put anything in it for help text. So, say I have a field "First Name." I can put a helptext connected to the Person model and the "first_name" field. I can also make something up, like "Something really confusing." The helptext is now associated with the Person model and the "Something really confusing" field. So, I can put it at the top of the form, instead of associating to a field with hard foreign keying. It can be anything arbitrary and will follow with that "field" anywhere. The hangup would be that you may change the name of the helptext field association inadvertently sending your original helptext into never land.
To make this easy, I created a TemplateTag, which I pass the name of the model and the name of the "field" I want to associate. Then anytime the template is rendered, that helptext is there, editable for anybody to get assistance with their user interface forms.
Not sure this is the best solution, but I couldn't really see any other way to do it, and got no responses.
Cheerio!

Tastypie accessing fields from inherited models

Is it possible to include fields on related models, using tastypie?
As per my models below: if I persist one VideoContent and one TextContent instance to the DB, I can then get 2 objects back from my Content resource, however none of the additional fields are available.
Is it possible to include fields from related models (in this instance, the video url and the text content) and will that cater for adding more Content types in the future without having to rewrite the Content Resource, or am I coming at this from the wrong direction?
The goal is to be able to extend this with more ContentTypes without having to make changes to the Content resource (assuming it's possible to get it working in the first place)
Models.py:
class Content(models.Model):
parent = models.ForeignKey('Content', related_name='children', null=True, blank=True)
class TextContent(Content):
text = models.CharField(max_length=100)
class VideoContent(Content):
url = models.CharField(max_length=1000)
And then my resources:
class ContentResource(ModelResource):
children = fields.ToManyField('myapp.api.resources.ContentResource', 'children', null=True, full=True)
class Meta:
resource_name = 'content'
queryset = ContentResource.objects.all()
authorization = Authorization()
always_return_data = True
I found a good solution in another answer
Populating a tastypie resource for a multi-table inheritance Django model
I've run into the same problem - although I'm still in the middle of solving it. Two things that I've figured out so far:
django-model-utils provides an inheritence manager that lets you use the abstract base class to query it's table and can automatically downcast the query results.
One thing to look at is the dehydrate/rehydrate methods available to Resource classes.
This is what I did:
class CommandResource(ModelResource):
class Meta:
queryset = Command.objects.select_subclasses().all()
That only gets you half way - the resource must also include the dehydrate/rehydrate stuff because you have to manually package the object up for transmission (or recieving) from the user.
The thing I'm realizing now is that this is super hacky and there's gotta be a better/cleaner way provided by tastypie - they can't expect you to have to do this type of manual repackaging in these types of situations - but, maybe they do. I've only got about 8 hours of experience with tastypie # this point so if I'm explaining this all wrong perhaps some nice stackoverflow user can set me straight. :D :D :D
I had the same requirement and finally solved it.
I didn't like the answer given in the above link because I didn't like the idea of combining queryset and re-sorting.
Apparently, you can inherit multiple resources.
By subclassing multiple resources, you include the fields of the resources.
And since those fields are unique to each resource, I made them nullable in the init.
wonder if there's a way to list the parents only once. (There are two now. One for subclassing, and one in meta)
class SudaThreadResource(ThreadResource):
def __init__(self, *args, **kwargs):
super(SudaThreadResource, self).__init__(*args, **kwargs)
for field_name, field_object in self.fields.items():
# inherited_fields can be null
if field_name in self.Meta.inherited_fields:
field_object.null=True
class Meta(ThreadResource.Meta):
resource_name = 'thread_suda'
usedgoodthread_fields = UsedgoodThreadResource.Meta.fields[:]
userdiscountinfothread_fields = UserDiscountinfoThreadResource.Meta.fields[:]
staffdiscountinfothread_fields = StaffDiscountinfoThreadResource.Meta.fields[:]
bitem_checklistthread_fields = BitemChecklistThreadResource.Meta.fields[:]
parent_field_set = set(ThreadResource.Meta.fields[:])
field_set = set(
set(usedgoodthread_fields) |
set(userdiscountinfothread_fields) |
set(staffdiscountinfothread_fields) |
set(bitem_checklistthread_fields)
)
fields = list(field_set)
inherited_fields = list(field_set - parent_field_set)
queryset = forum_models.Thread.objects.not_deleted().exclude(
thread_type__in=(forum_const.THREAD_TYPE_MOMSDIARY, forum_const.THREAD_TYPE_SOCIAL_DISCOUNTINFO)
).select_subclasses()

django error while updating only one field in model

I want to update only one field in my model. However, I am getting an error.
This is my model:
class People(models.Model):
name = models.CharField(max_length=100)
lastname = models.CharField(max_length=100)
class Salary(models.Model):
id_of_people=models.ForeignKey(People)
salary = models.IntegerField(required=False)
In views.py
-When I try this one to update :
def update(request):
a=Salary.objects.get(id_of_people_id=1)
a.salary=500
Salary().save()
My Error says:
IntegrityError at/update
salary.id_of_people_id may not be NULL
and traceback indicates:
Salary().save()
-When I try this one :
def update(request):
a=Salary.objects.get(id_of_people_id=1)
a.salary=500
Salary().save(save_fields=['salary'])
-I get this error:
save() got an unexpected keyword argument 'save_fields'
Can you please help me to update only one field in my table ?
In both of those cases you'll want to call save on the model instance you've created, not the model class--that is, you should be saving a, not Salary:
a.salary=500
a.save()
When you do Salary().save(), what's happening is that you create a brand new, empty model instance, and then try to commit that to the database, rather than committing the one that you had just modified.
If a ForeignKey is defined in your model, the contraint will be enforced at the db level so you will need to save the object reference by the Foreign key before you save the referring object.
You may also want to reconsider whether or not the foreign keys should be defined in person or Salary.
If you were to define the model like this:
class Person(models.Model):
name = models.CharField(max_length=100)
lastname = models.CharField(max_length=100)
salary = models.ForeignKey(Salary)
class Salary(models.Model):
amount = models.IntegerField(required=False)
Then you could define your views function so that it looks like this:
def update(request):
s = Salary(amount=request.POST['salary'])
s.save()
p = Person(name=request.POST['name'], lastname=request.POST['lastname'], salary=s)
p.save()
The nice about this is that you could then reference the salary from a Person instance:
Person.objects.get(pk=1).salary.amount
I can't help but ask the question though why you really need these in separate objects. Things might be simpler if your model looked like this:
class Person(models.Model):
name = models.CharField(max_length=100)
lastname = models.CharField(max_length=100)
salary = models.IntegerField(required=False)