I have two models:
class Building(models.Model):
label = models.CharField(_("Building label"), blank=False, max_length=255)
def get_absolute_url(self):
return reverse('buildings:config', kwargs={'pk': self.id})
class Floor(models.Model):
label = models.CharField(_("Floor label"), blank=True, max_length=255)
building = models.ForeignKey(Building, null=False)
I have written a custom ModelForm in order to allow the user to change (UpdateView) the building's infos and add a new floor (FloorsConfigForm) in the same page.
The FloorsConfigForm is :
class FloorsConfigForm(forms.ModelForm):
class Meta:
model = Floor
fields = ["label", "building",]
And the view is:
class BuildingConfigUpdateView(LoginRequiredMixin, UpdateView):
model = Building
fields = ('label','address',)
template_name = "buildings/building_update.html"
form_floors = FloorsConfigForm(prefix="floor_")
def get_context_data(self, **kwargs):
ret = super(BuildingConfigUpdateView, self).get_context_data(**kwargs)
ret["form_floors"] = self.form_floors
b = Building.objects.get(id=self.kwargs["pk"])
ret["floors"] = Floor.objects.filter(building=b)
return ret
def post(self, request, *args, **kwargs):
res = None
print(request.POST)
if "action_save" in request.POST:
res = super(BuildingConfigUpdateView, self).post(request, *args, **kwargs)
elif "action_add_floor" in request.POST:
# #add the floor , validate, save...
self.form_floors = FloorsConfigForm(request.POST, prefix="floor_")
if self.form_floors.is_valid():
self.form_floors.save()
res = HttpResponseRedirect(reverse('buildings:config', kwargs={"pk":self.kwargs["pk"]}))
else:
print(self.form_floors.errors)
res = HttpResponseRedirect(reverse('buildings:config', kwargs={"pk":self.kwargs["pk"]}))
else:
res = HttpResponseRedirect(reverse('buildings:config', kwargs={"pk":self.kwargs["pk"]}))
return res
This works very well, and I am happy(!)
BUT the last mile is to properly display the errors of my form_floors form in the template of its "parent".
And this is where I need you!
Note: in the view code above, I simply print the form error (print(self.form_floors.errors), and this is OK since it print
"<QueryDict: {'csrfmiddlewaretoken': ['gcBxn72bm6cLFXmpIatplZ0cNtsNgMlU'], 'floor_-building': [''], 'action_add_floor': [''], 'floor_-label': ['']}>
<ul class="errorlist"><li>building<ul class="errorlist"><li>This field is required.</li></ul></li></ul>"
when I dont specify a building to the floor.
The exact question is: how can I render this error message in the main template?
Edit - Getting closer
In my post def in view, I changed the Redirects:
def post(self, request, *args, **kwargs):
res = None
print(request.POST)
if "action_save" in request.POST:
res = super(BuildingConfigUpdateView, self).post(request, *args, **kwargs)
elif "action_add_floor" in request.POST:
# #add the floor , validate, save...
self.form_floors = FloorsConfigForm(request.POST, prefix="floor_")
if self.form_floors.is_valid():
self.form_floors.save()
res = HttpResponseRedirect(reverse('buildings:config', kwargs={"pk":self.kwargs["pk"]}))
else:
print(self.form_floors.errors)
res = super(BuildingConfigUpdateView, self).post(request, *args, **kwargs)
else:
res = HttpResponseRedirect(reverse('buildings:config', kwargs={"pk":self.kwargs["pk"]}))
return res
It's better, I have access to the errors in the template. But my main form is empty!
It's emplty because in my template, I have two html forms. So, when I send the second form, the POST request don't have the other kwargs.
Now, I need to find a way to "fusion" two requests!
Related
I am using a django (3.0) ModelMultipleChoice field for a form. I am trying to modify the queryset to make some restrictions on it.
here is the views :
def nouvelle_tache(request,id_livrable):
livrable=Livrable.objects.get(pk=id_livrable)
projet = livrable.projet
if request.method == "POST":
form = NouvelleTache(request.POST,projet=projet)
tache = form.save(commit=False)
tache.livrable = livrable
tache.id_tache = livrable.id_derniere_tache() + Decimal(0.01)
tache.save()
form.save_m2m()
etat = Temps_etat_tache(etat=form.cleaned_data['etat_initial'],tache=tache)
etat.save()
return redirect('tache',tache.pk)
else:
form = NouvelleTache(projet=projet)
return render(request, 'application_gestion_projets_AMVALOR/nouvelle_tache.html', locals())
And the forms :
class NouvelleTache(forms.ModelForm):
def __init__(self, *args, **kwargs):
projet = kwargs.pop('projet', None)
queryset = Utilisateur.objects.all()
for utilisateur in projet.utilisateurs:
queryset = queryset.exclude(pk=utilisateur.pk)
self.fields['ressources'].queryset = queryset
super(NouvelleTache, self).__init__(*args, **kwargs)
ressources= forms.ModelMultipleChoiceField(queryset=Utilisateur.objects.all() ,widget =forms.CheckboxSelectMultiple )
etat_initial = forms.ModelChoiceField(queryset=Etat_tache.objects.none())
class Meta:
model = Tache
fields = ['libelle']
I have the followig error : 'NouvelleTache' object has no attribute 'fields'
I don't understand why because many other users seems to have similar code and it works.
Any help would be appreciate.
super(NouvelleTache, self).__init__(*args, **kwargs)
needs to be executed first, as the fields are set in the super class:
def __init__(self, *args, **kwargs):
projet = kwargs.pop('projet', None)
queryset = Utilisateur.objects.all()
for utilisateur in projet.utilisateurs:
queryset = queryset.exclude(pk=utilisateur.pk)
super(NouvelleTache, self).__init__(*args, **kwargs)
self.fields['ressources'].queryset = queryset
How may I get the user details to use within a from? I know in the view I can just do:
currentUser=request.user
But if I use it in the form as so I get the following error "'request' is not defined".
class SelectTwoTeams(BootstrapForm):
currentUser=request.user
date_joined = currentUser.date_joined.replace(tzinfo=pytz.utc)
timeless30 = datetime.datetime.now() - datetime.timedelta(seconds=3610)
timeless30 = timeless30.replace(tzinfo=pytz.utc)
if date_joined > timeless30:
currentCharities = forms.ModelChoiceField(queryset=Charity.objects.filter(enabled=1), empty_label=None, widget=forms.Select(attrs={"class":"select-format"}))
team1 = forms.ModelChoiceField(queryset=StraightredTeam.objects.none(), empty_label=None,
widget=forms.Select(attrs={"class":"select-format"}))
team2 = forms.ModelChoiceField(queryset=StraightredTeam.objects.none(), empty_label=None,
widget=forms.Select(attrs={"class":"select-format"}))
Many thanks for any help in advance.
Below shows the init of this form just incase it may help. I know how to get access to the user data using kwargs for this part:
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
self.currentSelectedTeam1 = kwargs.pop('currentSelectedTeam1', None)
self.currentSelectedTeam2 = kwargs.pop('currentSelectedTeam2', None)
self.currentfixturematchday = kwargs.pop('currentfixturematchday', None)
self.currentCampaignNo = kwargs.pop('currentCampaignNo', None)
super(SelectTwoTeams, self).__init__(*args, **kwargs)
cantSelectTeams = UserSelection.objects.select_related().filter(~Q(fixtureid__fixturematchday=self.currentfixturematchday),campaignno=self.currentCampaignNo, )
if not cantSelectTeams:
queryset = StraightredTeam.objects.filter(currentteam = 1).order_by('teamname')
else:
queryset = StraightredTeam.objects.filter(currentteam = 1).exclude(teamid__in=cantSelectTeams.values_list('teamselectionid', flat=True)).order_by('teamname')
teamsAlreadyPlaying = StraightredFixture.objects.filter(soccerseason=1025, fixturematchday=self.currentfixturematchday, fixturedate__lte = timezone.now())
postponedGames = StraightredFixture.objects.filter(soccerseason=1025, fixturematchday=self.currentfixturematchday,fixturestatus = "P")
queryset = queryset.exclude(teamid__in=teamsAlreadyPlaying.values_list('home_team_id', flat=True)).order_by('teamname')
queryset = queryset.exclude(teamid__in=teamsAlreadyPlaying.values_list('away_team_id', flat=True)).order_by('teamname')
queryset = queryset.exclude(teamid__in=postponedGames.values_list('home_team_id', flat=True)).order_by('teamname')
queryset = queryset.exclude(teamid__in=postponedGames.values_list('away_team_id', flat=True)).order_by('teamname')
self.fields['team1'].queryset = queryset
self.fields['team2'].queryset = queryset
self.fields['team1'].initial = self.currentSelectedTeam1
self.fields['team2'].initial = self.currentSelectedTeam2
self.fields['team1'].label = False
self.fields['team2'].label = False
date_joined = user.date_joined.replace(tzinfo=pytz.utc)
timeless30 = datetime.datetime.now() - datetime.timedelta(seconds=3610)
timeless30 = timeless30.replace(tzinfo=pytz.utc)
if date_joined > timeless30:
self.fields['currentCharities'].label = False
The form class is defined when the module is loaded. That means that you can't set currentUser = request.user, since you don't have access to the request object yet. You should remove that line from your code.
The correct approach is to override the __init__ method so that it takes the user. If your field definitions depend on the user then you need to move them into the __init__ method.
class SelectTwoTeams(BootstrapForm):
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user')
super(SelectTwoTeams, self).__init__(*args, **kwargs)
date_joined = self.user.date_joined.replace(tzinfo=pytz.utc)
timeless30 = datetime.datetime.now() - datetime.timedelta(seconds=3610)
timeless30 = timeless30.replace(tzinfo=pytz.utc)
if date_joined > timeless30:
self.fields['currentCharities'] = forms.ModelChoiceField(queryset=Charity.objects.filter(enabled=1))
...
You should only use None as the default when popping if the user is not required. It is required in your case, since you access self.user.date_joined in the __init__ method. By storing the user as self.user, you can access it in other methods if required.
Finally, you need to change your view to pass the user when you instantiate the form.
if request.method == "POST"
form = SelectTwoTeams(request.POST, user=request.user)
...
else:
form = SelectTwoTeams(user=request.user)
You can overwrite the save method and send the request there.
viewys.py
if request.method == "POST"
if forms.is_valid():
form.save(request=request.user)
and in your forms.py:
def save(self, request=None, *args, **kwargs
self.currentUser = request.user
super(SelectTwoTeams, self).save(*args, **kwargs)
instance.save()
return instance
To get any variable in forms you must pass as kwarg argument then get-it in init
In the Form:
class someForm(forms.ModelForm):
...code...
def __init__(self, *args,**kwargs):
self.Name = kwargs.pop('SomeName')
in your views:
yourform = someForm(request.POST or None, initial={'foo': foo}, SomeName= someVar)
I have the following Model :
class Advertisement(models.Model):
slug = models.UUIDField(default=uuid4, blank=True, editable=False)
advertiser = models.ForeignKey(Advertiser)
position = models.SmallIntegerField(choices=POSITION_CHOICES)
share_type = models.CharField(max_length=80)
country = CountryField(countries=MyCountries, default='DE')
postal_code = models.CharField(max_length=8, null=True, blank=True)
date_from = models.DateField()
date_to = models.DateField()
Based on Advertiser, position, type country and postal code this stores adverisements with range date_from and date_to.
advertiser, position, share_type, country and postal_code
are coming from the request and are fetched in
class CreateAdvertisment(LoginRequiredMixin, CreateView):
# Some usefull stuff
def dispatch(self, request, *args, **kwargs):
self.advertiser = Advertiser.objects.get(user=self.request.user)
self.share_type = self.kwargs.get('share_type', None)
self.country = self.kwargs.get('country', None)
self.postal_code = self.kwargs.get('postal_code', None)
self.position = int(self.kwargs.get('position', None))
self.position_verbose = verbose_position(self.position)
ret = super(CreateAdvertisment, self).dispatch(request, *args, **kwargs)
return ret
Without any validation for checking date_from, date_to. I can simply do
def form_valid(self, form):
form.instance.advertiser = self.advertiser
form.instance.share_type = self.share_type
form.instance.country = self.country
form.instance.postal_code = self.postal_code
form.instance.position = self.position
ret = super(CreateAdvertisment, self).form_valid(form)
return ret
and I am done. Unfortunatly I cannot do this as I do have to check for valid time Frames for the Advertisment to avoid Double Bookings of the same time. I do this in the model with the following :
def clean(self):
ret = super(Advertisement, self).clean()
print ("country [%s] position [%s] share_type [%s] postal_code [%s]" % (self.country,
self.position, self.share_type, self.postal_code))
if self.between_conflict():
raise ValidationError("Blocks between timeframe")
elif self.end_conflict():
raise ValidationError("End occupied")
elif self.during_conflict():
raise ValidationError("Time Frame complete occupied")
elif self.start_conflict():
raise ValidationError("Start Occupied")
return ret
def start_conflict(self):
start_conflict = Advertisement.objects.filter(country=self.country,
position=self.position,
share_type=self.share_type,
postal_code=self.postal_code).filter(
date_from__range=(self.date_from, self.date_to))
return start_conflict
This works well and I filter out any Conflict for the given period. Problem is that I do not have the instance variables as they are set in view.form_valid() and model.clean() is called by the form validation process.
I do have an chicken egg problem here. I am thinking about setting the requests parameters to the form kwargs in
def get_form_kwargs(self, **kwargs):
kwargs = super(CreateAdvertisment, self).get_form_kwargs()
kwargs['advertiser'] = self.advertiser
kwargs['position'] = self.position
....
and then putting them into the form instance in form.init()
def __init__(self, *args, **kwargs):
advertiser = kwargs.pop('advertiser')
position = kwargs.pop('position')
# .. and so on
super(AdvertismentCreateForm, self).__init__(*args, **kwargs)
For some reasons I do not think this is very pythonic. Does anybody have a better idea? I will post my solution.
I think that overriding get_form_kwargs is ok. If all the kwargs are instance attributes, then I would update the instance in the get_form_kwargs method. Then you shouldn't have to override the form's __init__, or update the instance's attributes in the form_valid method.
def get_form_kwargs(self, **kwargs):
kwargs = super(CreateAdvertisment, self).get_form_kwargs()
if kwargs['instance'] is None:
kwargs['instance'] = Advertisement()
kwargs['instance'].advertiser = self.advertiser
...
return kwargs
In the model's clean method, you can now access self.advertiser.
alasdairs proposal works fine I have the following now :
def get_form_kwargs(self, **kwargs):
kwargs = super(CreateAdvertisment, self).get_form_kwargs()
if kwargs['instance'] is None:
kwargs['instance'] = Advertisement()
kwargs['instance'].advertiser = self.advertiser
kwargs['instance'].share_type = self.share_type
kwargs['instance'].country = self.country
kwargs['instance'].postal_code = self.postal_code
kwargs['instance'].position = self.position
return kwargs
def form_valid(self, form):
ret = super(CreateAdvertisment, self).form_valid(form)
return ret
Of course there is no need to override form_valid anymore. I have just included here in order to display that we do not set the instance fields anymore as this is done in get_form_kwargs() already
When I use this code to customize my form's widget, it will not validate. If I comment out def __init__(..) it works fine.
class CommentForm(forms.Form):
def __init__(self, *args, **kwargs):
self.wysiwyg = kwargs.pop('wysiwyg', False)
super(CommentForm, self).__init__()
if self.wysiwyg:
self.fields['comment_text'].widget = SummernoteWidget()
else:
self.fields['comment_text'].widget = forms.Textarea(attrs={'rows':2})
comment_text = forms.CharField()
I've been able to troubleshoot this far, and the difference between the working form (no init) and the invalid form, is this:
not valid form with init:
CommentForm bound=False, valid=Unknown, fields=(comment_text)
valid form:
CommentForm bound=True, valid=Unknown, fields=(comment_text)
Is bound the problem and how do I fix it?
Thanks!
Try this .. might work
class CommentForm(forms.Form):
def __init__(self, *args, **kwargs):
try:
self.wysiwyg = kwargs['wysiwyg']
except KeyError:
self.wysiwyg = None
super(CommentForm, self).__init__(*args, **kwargs)
if self.wysiwyg:
self.fields['comment_text'].widget = SummernoteWidget()
else:
self.fields['comment_text'].widget = forms.Textarea(attrs={'rows':2})
comment_text = forms.CharField()
I have an edit view for one of my models.
#login_required
def edit(request, id):
''' Edit form '''
if id:
post = get_object_or_404(Post, pk=id)
if post.user != request.user:
return HttpResponseForbidden()
else:
post = Post()
if request.POST:
form = PostForm(request.POST, instance = post)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('posts_manage'))
else:
form = PostForm(instance = post)
return render_to_response('posts/add.html', {'form':form}, context_instance=RequestContext(request))
Everything works fine, all the post information is loaded correctly, but one of the fields, which is a select box, is not being selected with the value obtained from the DB. Other select boxes are selected to the appropriate value.
The field that is not being populated properly in the model definition:
class Post(models.Model):
...
BATHROOM_CHOICES = ((1,'1'),(1.5,'1.5'),(2,'2'),(2.5,'2.5'),(3,'3'),(3.5,'3.5'),(4,'4'), (4.5,'4.5'),(5,'5+'))
bathrooms = models.DecimalField(max_digits = 2,decimal_places = 1,choices = BATHROOM_CHOICES)
Relevant section inside add.html:
{{ form.bathrooms|bootstrap}}
forms.py
class PostForm(ModelForm):
class Meta:
model = Post
exclude = ('available','user',)
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
return super(PostForm, self).__init__(*args, **kwargs)
def save(self, *args, **kwargs):
kwargs['commit'] = False
obj = super(PostForm, self).save(*args, **kwargs)
if self.request:
obj.user = self.request.user
obj.save()
return obj
The data in the DB is not being matched by a choice in BATHROOM_CHOICES
BATHROOM_CHOICES = ((1,'1'),(1.5,'1.5'),(2,'2'),(2.5,'2.5'),(3,'3'),(3.5,'3.5'),(4,'4'), (4.5,'4.5'),(5,'5+'))
and
models.DecimalField(max_digits = 2,decimal_places = 1,
are contradicting.
Your model definition expects all values will have a decimal place of at least 1, and probably coerces values like whole number from 1 to 1.0 in the DB (depending on adapter implementation).
so then when it looks for a choice matching the value 1 !== 1.0 and so no value is selected.
Possible fix:
BATHROOM_CHOICES = ((1.0,'1'),(1.5,'1.5'),(2.0,'2'),(2.5,'2.5'),(3.0,'3'),(3.5,'3.5'),(4.0,'4'), (4.5,'4.5'),(5.0,'5+'))