django m2m_changed with custom through model - django

In Django I do have two models "Author" and "Publication" that are connected with a Many-to-Many-Field, so that I can assign different authors to a publication. Additionally, I have to use a custom through-model "Authorship" to define the correct order.
class Author(models.Model):
first_name = models.CharField(max_length=48)
.....
class Authorship(models.Model):
author = models.ForeignKey(Author)
publication = models.ForeignKey('Publication')
order_of_authorship = models.IntegerField(default=1)
class Publication(models.Model):
title = models.CharField(max_length=128)
authors = models.ManyToManyField(Author, through=Authorship)
year = models.IntegerField(max_length=4)
...
citation_key = models.CharField(max_length=9, blank=True, default="")
At the moment I use the Admin Interface to populate my data with a form for the "Publication" and an inline form "Authorship".
What I want to achieve now:
An additional citation_key-field (e.g. "Einstein1950") should be auto-populated after data has changed.
What I tried to do:
I found out that using signals must be the best practice.
However the "m2m_changed"-Signal on "Publication.authors.through" is not fired, when I change the Authorships.
#receiver(m2m_changed, sender=Publication.authors.through)
def authors_changed(sender, **kwargs):
print("authors changed")
This problem is also discussed in a related topic, where the author seems to use "post_save" on the through-model.
#receiver(post_save, sender=Authorship)
def authorship_changed(sender, instance, **kwargs):
print("authors changed")
This seems to work out, but I have to keep in mind, that a deletion is not covered yet, so I added a post_delete-signal:
#receiver(post_delete, sender=Authorship)
def authorship_deleted(sender, instance, **kwargs):
print("authors deleted")
The problem now is: If I add 4 authors, I get that event fired 4 times. If I want to update my citation_key as described before, this happens also 4 times.
Can this be the correct solution? Or is there a better best practice? I assume it must work somehow with the m2m_changed signal, but I don't know how.
Since I am new to Django, I don't know if this is the obvious solution for you. Furthermore, in this scenario the unnecessary calculation should not have a huge impact, but it is not nice at all.
I only found a really old bug-report in the Django-Trac that seems to address this problem as well. But there is not solution yet.

This is a known bug, reported as ticket 17688 on Django.

Related

Django Many-to-many change in model save method does not work

Concept
First of all i have a pretty komplex model... The idea is of building a Starship as model. So every Ship has a type(ship_type) which is based on a Blueprint of this Shiptype. So when u're creating a Ship u have to decide which ship_type the model should use (foreign key).
Because I want to change a ship (example: buy another software) but not the blueprint itself, every Ship has the same fields in the database as the Blueprint Ship (both inherit from ShipField ). When someone set the ship_type or change it, I want Django to go to the blueprint get all informations and overwrite the information of the ship. So I tried to accomplish this behavior in the save method.
Function and error search
The function I wrote is always triggerd when there was a change in self.ship_type, so far so good. And all "normal" fields are changed too, only the many to many fields don't work.
I dove into it and got confused. Let us assume our ship has no entries in self.software but the new ship_type has 3. If I print out the self.software before the save (1.), I got as expected an empty queryset. When I do it after the super.save (2.) I got a queryset of three elements. So it seems to work everything. But if I take a look at the ship in the admin menu, the ship has no software at all.
Conclusion
So my conclusion is that somewhere after the save method (perhaps in the post_save event) the software get deleted again... At this point I need some help.
Ideas
I hope you guys understand what I'm trying to do here. I am not a database expert and can imagine that there are better ways to achieve this so I'm open for radical changes.
Models (simplified):
class ShipFields(models.Model):
body = models.ForeignKey(to=Body, verbose_name="Body", on_delete=models.SET_NULL,
null=True, blank=False, default=None)
software = models.ManyToManyField(Software, default=None, blank=True)
...
class ShipBlueprints(ShipFields, models.Model):
class Meta:
ordering = ['name']
verbose_name = "Ship Blueprint"
verbose_name_plural = "Ship Blueprints"
class Ship(ShipFields, models.Model):
name = models.CharField(max_length=256, unique=True)
ship_type = models.ForeignKey(to=ShipBlueprints, on_delete=models.SET_NULL, null=True)
...
__original_ship_type = None
def __init__(self, *args, **kwargs):
super(Ship, self).__init__(*args, **kwargs)
self.__original_ship_type = self.ship_type
def save(self, force_insert=False, force_update=False, *args, **kwargs):
# check if the ship type is changed
if self.ship_type != self.__original_ship_type:
...
# copy all fields from the related ShipBlueprints to the fields of Ship
# 1.
print(self.software.all())
self.body = self.ship_type.body
self.software.set(self.ship_type.software.all())
# or
# for soft in self.ship_type.software.all():
# self.software.add(soft)
# 2.
print(self.software.all())
...
super(Ship, self).save(force_insert, force_update, *args, **kwargs)
# 2.
print(self.software.all())
print(Ship.objects.get(name=self.name).software.all())
self.__original_ship_type = self.ship_type
I think i narrowed the problem down. When i change the ship_type via admin side, the many_to_many_fields dont get updated. BUT when i change the ship_type via the django shell it works perfectly!
When im using a form in a view it works, too. So my code works justfine but the admin page is somehow the problem... For me that's ok but it looks like an error in the saving method of admin, perhaps i will report this.
Thank you all for the ideas.
Hmm I believe it's because of __original_ship_type. It needs to be a foreign key to ShipBlueprints too. The __original_ship_type won't save anything inside of each row since it's not a database attribute/column. The __original_ship_type is simply part of the model for a database record.
That's actually one option. The other option is to use Django signals, specifically, pre_save. When you use pre_save, you are getting the NEW instance and you use this NEW instance's ID to do a DB query for the old instance. Afterwards, you save the object like that. The pre_save signal does not necessarily mean you HAVE to save something in there. It's just a signal calling a function.

Django ORM for mutual follow query

user_id user_follow
3 2
3 2
3 2
2 3
login user id=3, now i want to get people who follow me and i follow them, mutual follow in django framework for login user. above senario show login user(id=3) follow the user(id=2)
and user(id=2) also follow the login user(id=3) now I want to show to login user, how many user follow you and you follow him. using django orm
class Cause(models.Model):
description = models.TextField()
start_date = models.DateTimeField(null=True, blank=True)
creation_date = models.DateTimeField(default=datetime.now)
creator = models.ForeignKey(User, related_name='cause_creator_set')
attendees = models.ManyToManyField(User)
class Attendance(models.Model):
user = models.ForeignKey(User)
cause = models.ForeignKey(Cause)
user_follow = models.IntegerField(max_length=255)
registration_date = models.DateTimeField(default=datetime.now)
follow = models.BooleanField(default=False)
def __unicode__(self):
return "%s is attending %s" % (self.user.username, self.event)
class Meta(object):
verbose_name_plural = "Attendance"
There are multiple issues here, I'm going to try and address them all.
The relationship your models describe is what called a Extra field on ManyToMany relationship. Simply put, Cause has a ManyToMany to User, with Attendance being an intermediatry table. The benefits of using the through instruction for this are mostly for ease-of-access, and you can read the documentation I linked for examples. So:
attendees = models.ManyToManyField(User, through='Attendance')
Now let's talk about Attendance. I have a problem with two fields:
user = models.ForeignKey(User)
user_follow = models.IntegerField(max_length=255)
follow = models.BooleanField(default=False)
Yes, I showed 3 because only 2 of them are problematic. So you have a ForeignKey to User, right? But what is a ForeignKey? It's simply an IntegerField that points on the row (pk) on a different table. There are other stuff that happens here, but the basic thing is it's just an integerfield. And then you wanted to link that model to user again, right? So instead of adding another ForeignKey, you used an IntegerField.
Do you understand why that's a bad thing? I'll explain - you can't use the ORM on your own logic. Now the basic thing I'm assuming you are trying to accomplish here is actually this:
# let's pretend that User isn't built-in
class User(models.Model):
follow = models.ManyToManyField('self')
Then there's the follow Boolean which I really don't get - if a user is linked to another one, doesn't that automatically mean he's following him? seems more like a method than anything.
In anyway, I can think of two very valid options:
Use another ForeignKey. Really, if that what makes sense, there's nothing wrong with multiple ForeignKeys. In fact, it will create a sort of ManyToMany from user to himself going through Attendance as an intermediary table. I think this makes sense
Create your own User class. Not a terribly complicate feat. I also recommend this, since it seems you have plenty of customization. The built in User is great because it comes pretty much out of the box. But at the same time, the built-in user is pretty much built around the admin interface and kinda limits you a lot. A custom one would be easier. Then you can do the recursive relationship that seems to be what you're looking for, then implament is_following as a boolean method (seems more appropriate).
The last thing I'm gonna do is show you the method you're looking for. I'm showing you how to do it though as I mentioned, I strongly recommend you first take my suggestion and build your own User model. The method would look different of course, but it wouldn't be hard to convert.
That being said, the key here is simply creating two methods - one for checking if a user is related. A second one for checking if you're being followed by him. So if I understand you correctly, you're trying to achieve:
#using your way
def follows(a, b):
if Attendence.objects.filter(user__pk=a, user_follow=b).count() > 0:
return True
return False
def mutual(a, b):
if follows(a, b) and follows(b, a):
return True
return False
a, b = 2, 3
mutual(2, 3) # will output True
#using a custom User Class:
class MyUser(AbstractBaseUser):
# required fields here...
follow = models.ManyToManyField('self')
def is_following(self, user):
if user in self.follow.all():
return True
return False
def mutual(self, user):
if self.is_following(user) and user.is_following(self):
return True
return False

Form within a form in Django?

I have been looking at the documentation and thought maybe inline-formsets would be the answer. But I am not entirely sure.
Usually whenever you create a ModelForm it is bound to the related Model only. But what if you wanted to edit two models within a form?
In a nutshell, when editing the class conversation, and selecting a Deal class from the dropdown, I would like to be able to change the status of the selected deal class as well (but not the deal_name). All within the same form. Does Django allow that?
class Deal(models.Model):
deal_name = models.CharField()
status = models.ForeignKey(DealStatus)
class Conversation(models.Model):
subject = models.CharField()
deal = models.ForeignKey(Deal, blank=True, null=True)
Update:
The reason I wasn't sure if inline-formssets are the answer is the following behaviour:
View:
call = get_object_or_404(contact.conversation_set.all(), pk=call_id)
ConversationFormSet = inlineformset_factory(Deal, Conversation)
fset = ConversationFormSet(instance=call)
variables = RequestContext(request, {'formset':fset})
return render_to_response('conversation.html', variables)
Template
{{ formset }}
The result I am getting is not what I expected. I am getting three forms of Conversation class, where the first one is filled out (due editing and passing in the isntance). However the Deal DropDown menu is not listed at all. Why?
I found the solution and hope this will help someone else with the same problem in the future. I ended up redesigning my models.
I simply added the status also to my Conversation model.
class Conversation(models.Model):
subject = models.CharField()
deal = models.ForeignKey(Deal, blank=True, null=True)
status = models.ForeignKey(DealStatus)
In the view I added a custom save like this:
if form.is_valid():
call = form.save(commit=False)
deal = get_object_or_404(Deal.objects.all(), pk=call.deal.id)
deal.status = call.status
deal.save()
call.save()
That works nicely.
Another approach is to use signal like this:
def update_deal_status(sender, instance, created, **kwargs):
if created:
deal = Deal.objects.get(id__exact=instance.deal_id)
deal.status = instance.status
deal.save()
signals.post_save.connect(update_deal_status, sender=Conversation)

Django Models: Subclassing approach?

ists,
I'm looking for some validation on a subclassing approach. I have the following:
class Person(models.Model):
"""
Basic person
"""
user = models.ForeignKey(User) # hide
first_name = models.CharField(max_length=200)
last_name = models.CharField(blank=True, max_length=200)
class Meta:
verbose_name_plural = "People"
def __unicode__(self):
return u"%s, (%s)" % (self.first_name, self.user)
class Contributor(Person):
"""
Contributor
A Core contributor of the site content workflow
"""
class Meta:
verbose_name = 'contributor'
verbose_name_plural = 'contributors'
def get_articles(self):
"""
Return the articles that the author has published.
"""
return Article.objects.filter(self_in=authors)
class Member(Person):
"""
Member
A Member of the website.
"""
# Member history, payments etc...
joined = models.DateTimeField()
So, each Member or Contributor is a Person within the system, but it is possible for a Person to be 'None', 1 or both Member & Contributor, depending on their context.
This subclassing approach makes it simple to do things like:
#...
contributors = models.ManyToManyField(Contributor, help_text="Contributors/Authors to this article")
or
print Member.objects.all()
... and of course the usual efficiencies of subclassing, i.e. common fields and methods.
However, I'm wondering about the pros & cons of doing something like
class Person(models.Model):
"""
Person
"""
user = models.ForeignKey(User) # hide
first_name = models.CharField(max_length=200)
last_name = models.CharField(blank=True, max_length=200)
is_contributor = models.BooleanField()
is_member = models.BooleanField()
but then needing to filter things like
# Assuming this is possible...
contributors = models.ManyToManyField(Person.objects.filter(is_contributor=True), help_text="Contributors/Authors to this article")
With the subclassing approach, I wonder about the challenges of being aware of users that are People (Person), Members or Contributors - and being able to discern between.
i.e. its really easy to do if person.is_contributor: but perhaps more challenging
try:
Contributor.objects.get(person__user_id=request.user.id)
except:
no_access()
else:
let_them_in()
Apologies for the open-endness of this question -- it may have been more an opportunity to think out aloud.
First, there are two oddities about your model to begin with:
1) Why is Person -=> User a ForeignKey and not a OneToOne? Might a user be more than one person?
2) User already has first and last names - why also assign them to person?
Next, to the extent that your ultimate goal is the authorization depicted at the end, why not just use permissions? Then you won't need the boolean fields or the try - except at the end.
Fundamentally, I see nothing wrong with subclassing the User model. Folks in #django often fight over this, but if done right, it is one of the most time-saving and powerful steps you can take when you first sit down with your new django project.
Adding different subclasses of User with different attributes and different methods can very quickly give you a robust user environment with enormous auth possibilities. Thus far, however, it doesn't look like you have done anything that requires you to subclass User.

Copy all fields of a django model instance

ok i think this is very basic, but since I am new to Django I don't know how to handle this.
I need to copy an instance of a django-model. As explained here, there is a problem with copying ManyToMany relations. But the attachment "django-model-copying.diff" has that function I guess. So I don't know - does my Django already have that function? I don't know how to call it.
Help would be appreciated.
The docs include directions on how to do this - including how to handle many to many relationships.
You can just do the following:
m = MyModel.objects.get(pk=1)
m.id = None
m.save()
That way new instance will be created with new id of course in case of any unique properties it will trigger errors during validation.
NOTE:
As for the function you've mentioned - it is not yet added to the trunk, the status is design decision needed, but if you know what you're doing you can manually apply the diff to your django instance - it's called patching btw. Here are some details about how to do it: http://ariejan.net/2007/07/03/how-to-create-and-apply-a-patch-with-subversion/.
I'll try to answer your actual problem, because what you are asking for in the question is a problem with your proposed solution, which as many have pointed out is not really ideal.
In the comments you mention this:
i'll try to explain.. So we have 2 models: User and Book. A User has a
book called "Titanic" with some content. Now, another user wants a
relation to that book too. But, the second user wants exactly the same
book, but it should be called "Ship is going under".. I would copy the
book, and rename it. - I know, i could also put the content of the
book in another model - but my model is a little bit more complex.
Looks like you have three things to track:
Users
Their Books
Some custom notes that users have about their "owned" book.
For each Book, store its common information, for example:
class Author(models.Model):
name = models.CharField(max_length=50)
email = models.EmailField()
def __unicode__(self):
return unicode(self.name)
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.ManyToMany(Author)
isbn = models.CharField(max_length=13)
# and so on
def __unicode__(self):
return unicode(self.title)
Now, you need to store Book -> User relation, such as one user can have many books, two users may own the same book, with their own meta information attached.
class BookClub(models.Model):
username = models.ForeignKey(User)
book = models.ForeignKey(Book)
comments = models.TextField()
If you structure your models like that, you'll be able to do queries like:
"How many books does each member have?"
"What is the most popular book?"
"What is the least popular book?"
"What is the most popular author?"
It's worth noting that as of django 1.8 you may have UUIDFields. If you copy a model instance and then save it the unique constraint will fail on this column, in which case you have to do something like:
import uuid
m = MyModel.objects.get(pk=1)
m.pk = None
m.uuid = uuid.uuid4() //generate new uuid
m.save()