Django: saving a ModelForm with custom many-to-many models - python

I have Publications and Authors. Since the ordering of Authors matters (the professor doesn't want to be listed after the intern that contributed some trivial data), I defined a custom many-to-many model:
class Authorship(models.Model):
author = models.ForeignKey("Author")
publication = models.ForeignKey("Publication")
ordering = models.IntegerField(default=0)
class Author(models.Model):
name = models.CharField(max_length=100)
class Publication(models.Model):
title = models.CharField(max_length=100)
authors = models.ManyToManyField(Author, through=Authorship)
I've got aModelForm for publications and use it in a view. Problem is, when I call form.save(), the authors are obviously added with the default ordering of 0. I've written a OrderedModelMultipleChoiceField with a clean method that returns the objects to be saved in the correct order, but I didn't find the hook where the m2m data is actually saved, so that I could add/edit/remove the Authorship instances myself.
Any ideas?

If you are using a custom M2M table using the through parameter, I believe you must do the saves manually in order to save the additional fields. So in your view you would add:
...
publication = form.save()
#assuming that these records are in order! They may not be
order_idx = 0
for author in request.POST.getlist('authors'):
authorship = Authorship(author=author, publication=publication, ordering=order_idx)
authorship.save()
order_idx += 1
You may also be able to place this in your ModelForm's save function.

I'm not sure if there's a hook for this, but you could save it manually with something like:
form = PublicationForm(...)
pub = form.save(commit=False)
pub.save()
form.save_m2m()
So you can handle any custom actions in between as required. See the examples in the docs for the save method.

Related

Django many to many relation, include all IDs in queryset in both directions

I have 2 models connected via M2M relation
class Paper(models.Model):
title = models.CharField(max_length=70)
authors = models.ManyToManyField(B, related_name='papers')
class Author():
name = models.CharField(max_length=70)
Is there a way to include authors as all related authors' IDs (and maybe name somehow)?
Is there a way to include papers IDs as reverse relation (and maybe title as well)?
Author.objects.all().annotate(related_papers=F('papers'))
this only adds id of one paper, first one it finds I think.
Furthermore, changing related_papers to papers gives an error:
ValueError: The annotation ‘papers’ conflicts with a field on the
model.
From what I understand in your comments, you're using DRF. I will give you 2 answers.
1) If you're talking about model serializer, you can use PrimaryKeyRelatedField :
class AuthorSerializer(serializers.ModelSerializer):
papers=serializers.PrimaryKeyRelatedField(many=True, read_only=True)
class Meta:
model = Author
fields = ['name', 'papers']
class PaperSerializer(serializers.ModelSerializer):
class Meta:
model = Paper
fields = '__all__'
This will return the IDs for the other side of the relationship whether you're on Paper or Author side. That will return the primary keys, not a representation of the object itself.
2) Now you're also talking about performance (e.g. database hit at each iteration).
Django (not DRF-specific) has a queryset method to handle preloading related objects. It's called prefetch_related.
For example, if you know you're going to need the relation object attributes and want to avoid re-querying the database, do as follow:
Author.objects.all().prefetch_related('papers')
# papers will be already loaded, thus won't need another database hit if you iterate over them.
Actually, it has already been implemented for you. You should include a Many-to-Many relationship to author in your Paper model like this:
class Paper(models.Model):
title = models.CharField(max_length=70)
authors = models.ManyToManyField(Author, related_name='papers')
That gives you the opportunity to add Author objects to a related set using
p.authors.add(u), assuming that p is the object of Paper model, and a is an object of Author model.
You can access all related authors of a Paper instance using p.authors.all().
You can access all related papers of an Author instance using u.papers.all().
This will return an instance of QuerySet that you can operate on.
See this documentation page to learn more.

Any way to fetch the through fields for an object linked via Many2Many field without knowing the column name in advance?

I am trying to write a generic method that can take any Django Model and returns it in a dictionary form.
So for example, if my models are defined thus (very generic):
class A(models.Model):
somefieldA = models.TextField()
m2mfield = models.ManyToManyField(B, through='AandB')
def __unicode__(self):
return self.somefieldA
class B(models.Model):
somefieldB = models.TextField()
def __unicode__(self):
return self.somefieldB
class AandB(models.Model):
a = models.ForeignKey(A)
b = models.ForeignKey(B)
field1 = models.DecimalField()
field2 = models.TextField()
field3 = models.DateField()
Now, assume we have an instance of the object A a_obj.
I can get all the related B objects using:
# This loop is there because I am working with other fields as well.
def instance_to_dict(instance):
for field in instance._meta.get_fields():
if field.many_to_many:
m2m_mgr = getattr(instance, field.name)
for idx, assoc_obj in enumerate(m2m_mgr.all()):
assoc_obj_str = str(assoc_obj)
# How to obtain the related through field values?
# m2m_mgr.through.objects.get() would need prior knowlegde
# of field name so get(a=instance, b=assoc_obj) is not possible
# m2m_mgr.through.objects.all() fetches all the objects
# in the Many to Many manager.
And then call instance_to_dict(a_obj). This method could be called by passing other models' instances.
Ideally, I would like to create a dict of the obj and related "through" fields for any object. Is this possible to do?
In addition to the explicitly defined ManyToMany manager, there is also an implicit reverse relationship for the ForeignKey from AandB to A. So you can do something like this:
for field in instance._meta.get_fields(include_hidden=True):
if field.one_to_many: # reverse ForeignKey
m2m_through_mgr = getattr(instance, field.get_accessor_name()) # e.g. aandb_set
m2m_through_mgr.all() # all related instances from the through table
Another approach is to go through the through table fields looking at field.related_model to see which one points back to your original table.
This all gets quite messy, but there should be enough meta information to do what you want. One obstacle is that the API isn't fully documented. Specifically, relation fields are represented by instances of the ManyToOneRel class, which as of Django 2.1 remains undocumented for reasons hinted at in the source code. Hence my use of the undocumented get_accessor_name() method.

Formset for MainClass<-ForeignKey<-OneToOneField?

I need to process applications to an amateur sports event. An event has several distances/subclasses, each of them has some restrictions (age, etc).
My models are
class Event(models.Model):
title = models.CharField(max_length=255)
# more fields
class Klass(models.Model):
title = models.CharField(max_length=255)
capacity = models.IntegerField()
event = models.ForeignKey('Event', related_name="klasses")
# more fields
class TeamRestrictions(models.Model):
age_min = models.IntegerField()
age_max = models.IntegerField()
klass = models.OneToOneField(TeamRestrictions, related_name='restrict')
# more fields
I want to have a single page where a user creates a new event: names it, adds several subclasses into it and restrictions for every subclass. Well, without this one-to-one relationship, for just Event with several Klasses, I could use FormSet.
Of course, I could move all TeamRestrictions fields to Klass, but that looks ugly for me.
What should I use for this more complex structure?
You should create for each model a form and do it separately or you can create really sofisticated form which will do it for you.
This form then would have fields as title (Event), title (Klass), capacity, event, age_min ... so for the relation fields as ForeignKey you will have to use the ChoiceField which will be populated with choices in the __init__ function and so on. Then it should have good cleaning function so that it would have olny valid data and at the end the save. You will have to look if user has selected a field or is creating a new one (such as Event for Klass) and then process them and link and create everything. But that's not the best solution (even it could be in one step) but it is a choice. It could look great even if you added some javascript.

Using Django ModelForm with non-persistent model.Field

Is there a way to add a field to a Django model class such that:
It doesn't get persisted to the database (i.e. no column in the DB)
It does get rendered by a ModelForm
The widget for that field can be customised
I believe 3. can be done with a custom widget, and 2. will happen if the field inherits from models.Field. However, I haven't found a way to achieve 1. without breaking 2. and 3. I was hoping for a persist=False or db_column=None type of solution.
Scenario:
I'm using this to quickly produce data capture forms by only adding a class to the model, but in order to insert headers for sub sections I still having to edit the template. Was hoping to do the following:
models.py
from django.db import models
class Applicant(models.Model):
sectionA = models.SectionField(help_text="Personal details")
title = models.CharField(max_length=100)
name = models.CharField(max_length=100)
sectionB = models.SectionField(help_text="Banking details")
account = models.CharField(max_length=100)
pin = models.CharField(max_length=100)
In the above example, sectionA and sectionB are instances of a custom model.Field that doesn't actually get persisted but cause a heading to be rendered by the ModelForm and a custom widget
Finally:
I realise this probably violates separation of View and Model.
Other questions have been asked about non-persisting fields but their solutions don't render in a ModelForm
Sort of, Just don't make them a model field, theres no need for them to be.
sectionA = "Personal details"
sectionB = "Banking details"
You can access them via form.instance where you need them, you could even make them a form field instead of a string as I've shown here.

Django Form with a one-to-many relationship

I have a form in Django called PersonForm this forms model has a one-to-many relationship with Car. When displaying the PersonForm just like in the Django Admin I would like to allow my users to select/deselect from a list of Cars etc. Is this possible? I'm looking for information on where to start.
This is what I have so far for the PersonForm:
class PersonForm(forms.ModelForm):
class Meta:
model = Person
fields = ('description',)
The Models:
class Person(models.Model):
description = models.CharField(max_length="150")
class Car(models.Model):
make = models.CharField(max_length="25")
owner = models.ForeignKey('Person', related_name="Car")
So in the person form I need to show a list of cars that person is the owner of an allow selecting/deselecting of them. I'm assuming I can do this in the form i.e. using something like the related name.
Sounds like you want an inline model form. This give you the ability to add/remove Car objects from a Person within the Person form.
That previous link was for inlinemodeladmin. This next link is for an inline form:
https://docs.djangoproject.com/en/dev/topics/forms/modelforms/#modelforms-factory
I didn't have any chance with inline formset, so i would suggest to override your save method of the model, i feel it's more DRY:
class PersonForm(forms.ModelForm):
# add a field to select a car
car = forms.ModelChoiceField(car.objects.all())
class Meta:
model = Person
fields = ('description', 'car')
def save(self, commit=True):
instance = super().save(commit)
# set Car reverse foreign key from the Person model
instance.car_set.add(self.cleaned_data['car']))
return instance
I know this is an old thread, but since I found myself almost exclusively pointed here by google when searching, I thought I would include the following for anyone else looking for an answer.
The answer, I think, is to use
https://docs.djangoproject.com/en/3.1/ref/forms/fields/#modelchoicefield
or
https://docs.djangoproject.com/en/3.1/ref/forms/fields/#modelmultiplechoicefield
There is a good article on how to use the modelmultiplechoicefield at :
https://medium.com/swlh/django-forms-for-many-to-many-fields-d977dec4b024
But it works for one to many fields as well. These allow us to generate a form with multiple choices as checkboxes or similar widgets based upon a related field in a model.

Categories

Resources