This is a continuation of this article. A "project-like unit" is a "project" in a task management tool, etc., and here it is a "performance" because it manages plays.
---> GitHub repository (Python 3.8.1, Django 3.0.3)
Last time, we created "performances" and "performance users" and controlled access. We also made it possible for "users of a performance" to add data (actors) belonging to that performance.
This time, I would like to think about the interface for adding and editing data belonging to the performance.
For example, if you create a ForeignKey
field called" Casting "in the data" Characters "and refer to" Actors ", you want to be able to select only the actors for that performance.
However, just by creating Model
and View
as shown below, all actors (including actors belonging to other performances) can be selected as cast
.
models.py
from django.db import models
class Character(models.Model):
'''Character
'''
production = models.ForeignKey(Production, verbose_name='Performance',
on_delete=models.CASCADE)
name = models.CharField('Role name', max_length=50)
cast = models.ForeignKey(Actor, verbose_name='Casting',
on_delete=models.SET_NULL, blank=True, null=True)
views.py
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.edit import CreateView
from .models import Character
class ChrCreate(LoginRequiredMixin, CreateView):
'''Additional view of Character
'''
model = Character
fields = ('name', 'cast')
▲ You can choose the actors of other performances.
Consider adding a "character" to a "performance". "Characters" has a field called "Casting", and "refer to" actor data "belonging to the performance".
The above illustration is "an example where the actors of other performances can choose", but except for that problem, the UI is fine.
Considering that the request sent from the web browser may have been tampered with, not only the UI choices but also the "accepted value" should be limited to "only the actor of the performance".
Think about when you want to edit the added "characters" later. It would be the original Mokuami if the affiliation (performance) of the character who cast the "actor of the performance" could be changed. It seems good to make it impossible to change the affiliation of the "character" once created.
This app uses a class-based view (CreateView
), so the form is automatically generated and you can write the template as follows:
modelname_form.html
<form method="post">
{% csrf_token %}
<table>
{{ form }}
</table>
{% if object %}
<input type="submit" value="update">
{% else %}
<input type="submit" value="add to">
{% endif %}
</form>
Since the input UI of each field of the object is expanded in this part called {{form}}
, you can also rewrite it.
In that case, you need to pass the cast
choice as context
and also separate the form writing style in the template for each field.
Here, we will stop it and modify the contents of the form expanded in the {{form}}
part in advance.
Another way to modify the contents of a form is to create a subclass of the form, but you can modify it without doing so. Here, I will modify it in the method of the view. We'll talk about subclassing forms later.
Since a form is a type of context
passed from a view to a template, you can overrideget_context_data ()
and modify it as follows:
ProdBaseCreateView
, which the view inherits in the code below, is an abstract class that receives the performance ID and controls access (see Previous article for details). Please see. You don't have to worry too much now).
rehearsal/views/views.py
class ChrCreate(ProdBaseCreateView):
'''Additional view of Character
'''
model = Character
fields = ('name', 'cast')
def get_context_data(self, **kwargs):
'''Modify the parameters passed to the template
'''
context = super().get_context_data(**kwargs)
#Show only the actors of the performance
actors = Actor.objects.filter(production=self.production)
#Create a choice
choices = [('', '---------')]
choices.extend([(a.id, str(a)) for a in actors])
#Set in Form
context['form'].fields['cast'].choices = choices
return context
--context ['form']
allows you to see the form the view is trying to pass to the template.
--If you set choices
in a field that the form has, it will be displayed as a choice.
--Each element of choices
is a tuple of" value "and" display string ".
--This app allows you to set cast
to empty, so the value of the first option is empty.
▲ Only the actors of the performance to which the characters belong were displayed.
I see information that setting choices
in a field on a form automatically repels other values, but when I actually tried it, it didn't.
It may be because the field with choices
was ForeignKey
.
▲ Try tampering with the options in your browser.
▲ The falsified value has been added.
You can override the view's methods to modify the validation results. In the end, it's cleaner to create a subclass of the form, but first let's do it with just the view.
rehearsal/views/views.py
class ChrCreate(ProdBaseCreateView):
# (Omission)
def form_valid(self, form):
'''When passed validation
'''
#Validation failed if cast is not an actor in the same performance
instance = form.save(commit=False)
if instance.cast.production != self.production:
return self.form_invalid(form)
return super().form_valid(form)
form_valid ()
is a view-side processing method that is called when the form passes validation.
If you forcibly call the method (form_invalid ()
) that is called when the validation is not passed, you can prevent the save.
--form.save (commit = False)
gets the object that the form is trying to save. If you set commit = True
, it will be saved and then retrieved.
--You can also get objects with self.object
and form.instance
, but I don't really understand the difference. I would be grateful if anyone who knows this could let me know.
--self.production
in the ʻifstatement is an attribute of the view's superclass
ProdBaseCreateView`, which has the" performance "of the object you are trying to save (see [For more information]. See the previous article](https://qiita.com/satamame/items/959e21ade18c48e1b4d6).
You may find it unpleasant to call form_invalid ()
from form_valid ()
as above.
If you think of the meaning of form_valid ()
as "the place where it is called when it passes the validation and performs the subsequent processing", it is because there is a sense of incongruity like an act of over-righting.
As a result, form_invalid ()
is called, and I feel that such a chabudai return is allowed.
In the worst case scenario, you could allow form_invalid ()
to call form_valid ()
as well, resulting in a circular call.
Of course, if you decide on rules such as making it one-way, there is no problem with the above method.
If you're subclassing your form, you don't even need to override the get_context_data ()
you did in "Modifying the UI".
Instead, the view overrides a method called get_form_kwargs ()
.
It is an image that customizes the generated form itself, instead of modifying the "form to be passed to the template".
The return value that can be obtained with get_form_kwargs ()
is a dictionary of keyword arguments passed to the form constructor.
So if you add an entry to this, you can use it on the form side.
The following is an example of overriding get_form_kwargs ()
assuming a class called ChrForm
is defined.
If you set form_class
in the view, don't set fields
(I think it would have resulted in an error).
rehearsal/views/views.py
from rehearsal.forms import ChrForm
class ChrCreate(ProdBaseCreateView):
'''Additional view of Character
'''
model = Character
form_class = ChrForm
def get_form_kwargs(self):
'''Modify the information passed to the form
'''
kwargs = super().get_form_kwargs()
#Pass production because it is used for validation on the form side
kwargs['production'] = self.production
return kwargs
rehearsal/forms.py
from django import forms
from production.models import Production
from .models Character
class ChrForm(forms.ModelForm):
'''Character addition / update form
'''
class Meta:
model = Character
fields = ('name', 'cast')
def __init__(self, *args, **kwargs):
#Extract the parameters added in view
production = kwargs.pop('production')
super().__init__(*args, **kwargs)
#Casting can only be selected by actors of the same performance
queryset = Actor.objects.filter(production=production)
self.fields['cast'].queryset = queryset
You are setting queryset
instead of choices
in the cast
field.
This will allow you to generate and validate choices based on the query set you have set.
If you create this form, you can use it not only for the additional view but also for the update view.
Now, let's tamper with the options we did in "Validation".
Prohibit changes to production
in these add / update views so that you cannot change the" performance "of the" characters "or" actors ".
If you normally create a form with model
in Django, all the fields will be editable, so you need to specify the editable fields.
--If you want to specify form_class
in the view, as you did in Form Subclassing, specify fields
in the form's Meta
.
--If you do not specify form_class
, specify fields
in the view.
You may also want to reference fields that you do not edit from the form or template.
In either case, as we've mentioned, you can use the views get_context_data ()
and get_form_kwargs ()
.
--I figured out how to make production
the same when associating two types of data ("characters" and "actors"), both of which have a ForeignKey
field called production
.
--Although it was possible to modify both the UI and validation on the view side, subclassing the form resulted in a cleaner implementation.
--Overridden the get_context_data ()
method when passing data from the view to the template.
--Overridden the get_form_kwargs ()
method when passing data from the view to the form.
--To specify input value choices on the form side, set the field queryset
and both the UI and validation followed suit.
-Django divides data into project units (3)
Recommended Posts