Prepopulated Many-to-Many relationship in Django form - django

I am working on an app involving Candidates. During his application, each candidate has to answer some open questions (like "Why do you want to join ?").
The questions asked to the candidate are not predefined in the application, I want the admin to be able to create or modify questions that will be asked to future candidates.
In order to do that, I implemented the following models :
class Candidate(models.Model):
# (...)
answers = models.ManyToManyField('Question', through='Answer')
class Question(models.Model):
text = models.CharField(max_length=200)
class Answer(models.Model):
candidate = models.ForeignKey(Candidate, on_delete=models.CASCADE)
question = models.ForeignKey(Question, on_delete=models.CASCADE)
text = models.CharField(max_length=100)
The idea is to store the questions created by the admin in a model, and store the answers given by the candidate in the intermediate table Answer
I have then created some forms for the models :
class CandidateForm(forms.ModelForm):
class Meta:
model = Candidate
fields = ['first_name', 'last_name', 'birth_date', 'email']
class AnswerForm(forms.ModelForm):
class Meta:
model = Answer
fields = ['question', 'text']
AnswerInlineFormset = forms.inlineformset_factory(Candidate, Answer, form=AnswerForm)
And I use a CreateView to display the form:
class CandidateFormView(CreateView):
template_name = 'candidates/candidate.html'
form_class = CandidateForm
def get_context_data(self, **kwargs):
context = super(CandidateFormView, self).get_context_data(**kwargs)
context['answers_formset'] = AnswerInlineFormset
return context
For now the form only display the field from the Candidate model (first name, last name and so on), and the formset with Question select field with all the available questions and the text field empty for the answer. This is expected, the formset is not populated with any data, so it just display fields to allow the user to define the relationship.
Of course, I don't want the user to select which question he wants to reply ! So I want to prepopulate the formset with the questions from the database (and use them as label instead as a field, but I think this part is not too difficult)
Something like that (with the questions filled from the Question model):

Related

When don't we use "__all__" in ModelSerializer in Django Rest Framework

This is just my curiosity but I will be very happy if anyone answers my question.
I am using Django Rest Framework but I'm a beginner. In serializers.py, I use ModelSerializer and "all" to fields attribute.
This is an example.
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = "__all__"
And then, I just thought
when don't we use "__all__" in serializers.py??
As long as we create models.py in advance, I think we usually use all fields in each Model.
I would like you to teach me when we omit specific fields that come from each Model.
Thank you.
So the second question is a bit harder to explain in a comment:
If we use some fields of all fields in Model, how do we store information of the rest of fields?
Various cases:
Fields with defaults:
class Log(models.Model):
message = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
class LogSerializer(serializers.ModelSerializer):
class Meta:
model = Log
fields = ('message',)
For autogenerated, think user profile models via the post_save signal or calculated fields:
class OrderLine(models.Model):
order = models.ForeignKey(Order)
name = models.CharField(max_length=200)
quantity = models.IntegerField()
price = models.DecimalField()
class OrderLineSerializer(serializers.ModelSerializer):
order = serializers.PrimaryKeyRelatedField()
product = serializers.IntegerField()
class Meta:
model = OrderLine
fields = ('quantity', 'product', 'order')
In this case, the product is a primary key for a product. The serializer will have a save method that looks up the product and put it's name and price on the OrderLine. This is standard practice as you cannot reference a product in your orders, else your orders would change if you change (the price of) your product.
And derived from request:
class BlogPost(models.Model):
author = models.ForeignKey(User)
post = models.TextField()
class BlogPostSerializer(serializers.ModelSerializer):
class Meta:
model = BlogPost
fields = ('post',)
def create(self, validated_data):
instance = BlogPost(**validated_data)
instance.author = self.context['request'].user
instance.save()
return instance
This is pretty much the common cases.
There are many cases, but I think the two main ones are:
When you don't want all fields to be returned by the serializer.
When you need some method of the serializer to know its fields. In such case, you should traverse fields array, but it doesn't work if you use __all__, only if you have an actual list of fields.

Django Identifying Class Based View and Form Multiple Models

I'm on my second Django project. On my first project I used all generic views, with only the most basic forms tied directly to a custom user model using UpdateView.
In this project I'm trying to implement user profile functionality. My custom user model has some extra dummy fields just so I can manipulate the data. This I refer so as the CustomUser model. I also have a UserAddress model containing addresses since a user can have more than one address. I have tried looking for other questions, and I get similar questions, but there is always something missing:
Django class based views - UpdateView with two model forms - one submit
Using Multiple ModelForms with Class-Based Views
Multiple Models in a single django ModelForm?
Django: multiple models in one template using forms
I've spent the last day or two looking for the "Django way" of doing what I want to do and have had no luck. My initial thought was that I could use a single template, fed with two ModelForms wrapped in a single <form> tag. The view would then process each form and update the CustomUser model and create or update the UserAddress models. I have figured out how to mash together the functionality using the base View CBV, but I suspect I'm duplicating a lot of functionality that I could probably find already done in Django. This is my view, where I handle the form instantiating manually, and feed the context.
class UserDetailsView(View):
def get(self, request):
user = request.user
user_basic = CustomUser.objects.get(pk=user.pk)
basic_form = UserBasicForm(instance=user_basic)
user_address = UserAddress.objects.get(user=user.pk)
billing_address_form = UserAddressForm(instance = user_address)
context = {'basic_form':basic_form,'billing_address_form':billing_address_form}
return render(request, 'mainapp/profile.html', context)
My post is the same at this point, as I haven't done the actual validation and saving yet.
class UserBasicForm(forms.ModelForm):
class Meta(forms.ModelForm):
model = CustomUser
fields = (
'username',
'first_name',
'last_name',
)
labels = {
'username':'Username',
'first_name':'First Name',
'last_name':'Last Name',
}
class UserAddressForm(forms.ModelForm):
class Meta(forms.ModelForm):
model = UserAddress
fields = (
'description',
'addressee',
'company',
'address_1',
'address_2',
'city',
'prov_state',
'post_zip',
'country',
)
labels = {
'description':'Address Description',
'addressee':'Addressee',
'company':'Company Name',
'address_1':'Address',
'address_2':'Address 2',
'city':'City',
'prov_state':'Province or State',
'post_zip':'Postal or Zip Code',
'country':'Country',
}
class CustomUser(AbstractUser):
objects = CustomUserManager()
def __str__(self):
return self.email
class UserAddress(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE,)
description = models.CharField(max_length = 256, default='Description')
addressee = models.CharField(max_length = 256,)
company = models.CharField(max_length = 256, default='Self')
address_1 = models.CharField(max_length = 256,)
address_2 = models.CharField(max_length = 256,)
city = models.CharField(max_length = 256,)
prov_state = models.CharField(max_length = 256,)
post_zip = models.CharField(max_length = 256,)
country = models.CharField(max_length = 256,)
def __str__(self):
return self.description
Please go easy on me, I'll take most advice offered.
Edit
After reviewing some other SO questions and Django form examples, it appears that the final answer probably isn't SO material. That said, my observation is that for the Django built-in CBVs, the "best" base view is that which you can minimize or simplify the code you add. Using a TemplateView or FormView for my project in this case just depends on which methods I choose to re-write or override and for that, I'm still open to suggestions.
I'd do something like this (with betterforms):
class UserCreationMultiForm(MultiModelForm):
form_classes = {
'basic': UserBasicForm,
'address': UserAddressForm,
}
class UserDetailsView(View):
template = "mainapp/profile.html"
form_class = UserCreationMultiForm
success_url = reverse_lazy('home')
def form_valid(self, form):
# Save the user first, because the profile needs a user before it
# can be saved.
user = form['basic'].save()
address = form['address'].save(commit=False)
address.user = user
address.save()
return redirect(self.get_success_url())
Then rename your forms in your template to form.basic and form.address

Django admin custom inline fails with "Please Correct the Error Below"

I have a model class where i am using the django admin and it has a few inline forms. I cannot save the change form for this model. It always complains with "Please correct the error below" even though there is no error highlighted on the page.
I have narrowed it down to the inline that is causing problems: HorizontalInline When i remove this one everything works.
Here are the relevant fields of my model:
class Equipment(models.Model):
name = models.CharField(max_length=14)
description = models.CharField(max_length=100)
class HorizontalInterface(models.Model):
name = models.CharField(max_length=100, blank=True,)
us_equip = models.ForeignKey(Equipment, related_name='upstream')
ds_equip = models.ForeignKey(Equipment, related_name='downstream')
Here are the relevant fields of my admin.py:
class HorizontalInlineFormSet(BaseInlineFormSet):
def __init__(self, *args, **kwargs):
super(HorizontalInlineFormSet, self).__init__(*args, **kwargs)
self.queryset = HorizontalInterface.objects.filter(Q(us_equip=self.instance) | Q(ds_equip=self.instance))
class HorizontalInline(admin.TabularInline):
model = HorizontalInterface
formset = HorizontalInlineFormSet
show_change_link = True
extra = 0
fk_name = 'us_equip'
class EquipmentAdmin(admin.ModelAdmin):
list_display = ('name', 'description')
inlines = [
HorizontalInline
]
I believe the complexity that it cannot handle is related to the fact that my horizontal interface model has two Equipment foreign keys and i would like to display all interfaces both upstream and downstream related to the equipment in question but also be able to edit them and save the equipment.
Any ideas how i can fix the save on my equipment model?
Thanks in advance for any help.

Validate a condition that relies on foreign keys in Django admin

I have two models, Book and Page, where Page has a foreign key relation to Book. In admin.py, the webpage to modify a book has inline elements for Page objects.
models.py:
class Book(models.Model):
# ...
class Page(models.Model):
book = models.ForeignKey(Book, editable=False)
number = models.IntegerField('Page number')
# ...
admin.py:
class PageAdminInline(admin.TabularInline):
model = Page
extra = 1
class BookAdmin(admin.ModelAdmin):
inlines = [PageAdminInline]
admin.site.register(Book, BookAdmin)
In the inline forms, a field allows to set the page number for each page. With this configuration, how can I validate the global condition that all pages numbers of a book should be different and numbered from one to the number of Page objects associated to the book?
I suppose I have to override a clean() method related to a book model or form somewhere, but I don't know how to access from there the data related to the pages that the user inputs.
Edit
Based on Emett's suggestion, I have tried to override the clean() method of the Page model:
class Page(models.Model):
book = models.ForeignKey(Book, editable=False)
number = models.IntegerField('Page number')
def clean():
book_pages = Page.objects.filter(book=self.book)
# ... [apply condition on the book_pages QuerySet]
super(Page, self).clean()
This does not work: if I modify the page number of all pages in the admin site for a book, book_pages will contain objects that have the old page numbers.
In addition, having the condition checked in Page also means that it will be tested n times if I have n pages, while just checking it once should be sufficient.
An easier solution would be putting unique_together in django model. IE
class Page(models.Model):
book = models.ForeignKey(Book, editable=False)
number = models.IntegerField('Page number')
class Meta:
app_label = 'page'
db_table = 'pages'
verbose_name = 'Page'
verbose_name_plural = 'Pages'
unique_together = (('book', 'number'),)
Another workaround if you don't want to use unique_together, then create a form, use it in inline, ie:
class PageForm(forms.ModelForm):
class Meta:
model = Page
fields ='__all__'
def clean():
cleaned_data = self.cleaned_data
book = cleaned_data.get('book')
number = cleaned_data.get('number')
page_qset = Page.objects.filter(book=book, number=number)
if len(page_qset) > 0: # inefficient solution, using it for forcibly executing query
raise forms.ValidationError('Already exists')
return super().clean()
class PageAdminInline(admin.TabularInline):
form = PageForm

In Django, how to implement a form to assign multiple users to an item?

I have two models that I need to create a form for.
def Item(models.Model):
title = models.CharField()
description = models.TextField()
def ItemUser(models.Model):
item = models.ForeignKey(Item)
user = models.ForeignKey(User)
I need to make a form where a user can create a new Item and be able to add/remove multiple ItemUser. Since I am using email address as a user id, I plan to have that entered as input for ItemUser. So the form should render input boxes for taking email addresses and then on the backend I would need to fetch user ids for these email addresses to save into ItemUser table.
How do I implement this form in Django?
EDIT:
Adding another model example to add clarity.
def Blog(models.Model):
creator = models.ForeignKey(User)
title = models.CharField()
description = models.TextField()
def BlogAccessUser(models.Model):
blog = models.ForeignKey(Blog)
access_user = models.ForeignKey(User)
Each blog has a list of users that are allowed to access it. So in the form a user will create a blog and also add multiple users to this blog. Something like "Share" feature in Google DOCS where I can add multiple users to my document.
where a user can create a new item
So this would suggest an Item belongs to one user, one-to-one relationship. So lets start off with this...
def Item(models.Model):
title = models.CharField()
description = models.TextField()
user = models.OneToOneField(User, related_name="item")
be able to add/remove multiple ItemUser
This would suggest a many-to-many relationship so you will need to add this also to item model...
def Item(models.Model):
title = models.CharField()
description = models.TextField()
user = models.OneToOneField(User, related_name="item")
item_users = models.ManyToManyField(ItemUser, related_name="item")
I need to make a form
So for this you create a model form, which you can filter on email, as you can see email is passed when you create the form init.
class ItemForm(forms.ModelForm):
class Meta:
model = Item
def __init__(self, email=None, *args, **kwargs):
super(ItemForm, self).__init__(*args, **kwargs)
# Representing the many to many related field in Item
ItemUsers = forms.ModelMultipleChoiceField(queryset=ItemUser.objects.filter(user__email=email))
self.fields['item_users'] = forms.ModelChoiceField(queryset=ItemUsers)
Thats it, in your view just pass the email for filtering, form = itemForm(email=email)
Above was freehand so there could be a few mistakes in their, but it should give you the right idea, hope this helps.
The answer to the original question was to use formsets in Django. I ended up using an InlineFormset and writing a custom render method for the form. I also used JS to dynamically add/remove forms.