Unique constraint failed - redirect view? - django

I managed to get the below code limited to 1 user review per restaurant and it works great using the class meta "unique together"
class UserReview(models.Model):
# Defining the possible grades
Grade_1 = 1
Grade_2 = 2
Grade_3 = 3
Grade_4 = 4
Grade_5 = 5
# All those grades will sit under Review_Grade to appear in choices
Review_Grade = (
(1, '1 - Not satisfied'),
(2, '2 - Almost satisfied'),
(3, '3 - Satisfied'),
(4, '4 - Very satisfied'),
(5, '5 - Exceptionally satisfied')
)
restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE)
user_review_grade = models.IntegerField(default=None, choices=Review_Grade) # default=None pour eviter d'avoir un bouton vide sur ma template
user_review_comment = models.CharField(max_length=1500)
posted_by = models.ForeignKey(User, on_delete=models.DO_NOTHING)
class Meta:
unique_together = ['restaurant', 'posted_by']
I now realise that I need to update my view so that is this constraint is failed I am taken to an error page, but I can't find how, any guidance would be appreciated
View:
class Reviewing (LoginRequiredMixin, CreateView):
template_name = 'restaurants/reviewing.html'
form_class = UserReviewForm
# Get the initial information needed for the form to function: restaurant field
def get_initial(self, *args, **kwargs):
initial = super(Reviewing, self).get_initial(**kwargs)
initial['restaurant'] = self.kwargs['restaurant_id']
return initial
# Post the data into the DB
def post(self, request, restaurant_id, *args, **kwargs):
form = UserReviewForm(request.POST)
restaurant = get_object_or_404(Restaurant, pk=restaurant_id)
if form.is_valid():
review = form.save(commit=False)
form.instance.posted_by = self.request.user
print(review) # Print so I can see in cmd prompt that something posts as it should
review.save()
# this return below need reverse_lazy in order to be loaded once all the urls are loaded
return HttpResponseRedirect(reverse_lazy('restaurants:details', args=[restaurant.id]))
return render(request, 'restaurants/oops.html')
Form:
# Form for user reviews per restaurant
class UserReviewForm(forms.ModelForm):
class Meta:
model = UserReview
# restaurant = forms.ModelChoiceField(queryset=Restaurant.objects.filter(pk=id))
fields = [
'restaurant',
'user_review_grade',
'user_review_comment'
]
widgets = {
'restaurant': forms.HiddenInput,
'user_review_grade': forms.RadioSelect,
'user_review_comment': forms.Textarea
}
labels = {
'user_review_grade': 'Chose a satisfaction level:',
'user_review_comment': 'And write your comments:'
}

In case the form is not valid, you can redirect to an error page:
from django.db import IntegrityError
class Reviewing (LoginRequiredMixin, CreateView):
template_name = 'restaurants/reviewing.html'
form_class = UserReviewForm
# Get the initial information needed for the form to function: restaurant field
def get_initial(self, *args, **kwargs):
initial = super(Reviewing, self).get_initial(**kwargs)
initial['restaurant'] = self.kwargs['restaurant_id']
return initial
# Post the data into the DB
def post(self, request, restaurant_id, *args, **kwargs):
form = UserReviewForm(request.POST)
restaurant = get_object_or_404(Restaurant, pk=restaurant_id)
if form.is_valid():
review = form.save(commit=False)
form.instance.posted_by = self.request.user
print(review) # Print so I can see in cmd prompt that something posts as it should
try:
review.save()
except IntegrityError:
return redirect('name-of-some-view')
# this return below need reverse_lazy in order to be loaded once all the urls are loaded
return redirect('restaurants:details', restaurant.id)
return redirect('name-of-some-view')
That being said, you doo too much in your view. Django's CreateView is designed to remove most of the boilerplate code for you.
You thus can implement this as:
class Reviewing (LoginRequiredMixin, CreateView):
template_name = 'restaurants/reviewing.html'
form_class = UserReviewForm
# Get the initial information needed for the form to function: restaurant field
def get_initial(self, *args, **kwargs):
initial = super(Reviewing, self).get_initial(**kwargs)
initial['restaurant'] = self.kwargs['restaurant_id']
return initial
def form_valid(self, form):
form.instance = self.request.user
return super().form_valid(form)
def get_success_url(self, **kwargs):
return reverse('restaurants:details', args=[self.kwargs['restaurant_id']])
def form_invalid(self, form):
return redirect('name-of-some-view')

Related

How to perform queries in django modelform?

I tried this in my modelform:
class Ledgerform(forms.ModelForm):
class Meta:
model = ledger1
fields = ('name', 'group1_Name')
def __init__(self, User, Company, *args, **kwargs):
self.User = kwargs.pop('User', None)
self.Company = kwargs.pop('Company', None)
super(Ledgerform, self).__init__(*args, **kwargs)
self.fields['name'].widget.attrs = {'class': 'form-control',}
self.fields['group1_Name'].queryset = group1.objects.filter(User= self.User,Company = self.Company)
In my views.py I have done something like this:
class ledger1ListView(LoginRequiredMixin,ListView):
model = ledger1
paginate_by = 15
def get_queryset(self):
return ledger1.objects.filter(User=self.request.user, Company=self.kwargs['pk'])
class ledger1CreateView(LoginRequiredMixin,CreateView):
form_class = Ledgerform
def form_valid(self, form):
form.instance.User = self.request.user
c = company.objects.get(pk=self.kwargs['pk'])
form.instance.Company = c
return super(ledger1CreateView, self).form_valid(form)
I want to perform the the same query that I have passed in my ledger1ListView by using queryset in my modelform but my kwargs.pop is not returning the current user or the company...
This is my models.py:
class ledger1(models.Model):
User = models.ForeignKey(settings.AUTH_USER_MODEL,on_delete=models.CASCADE,null=True,blank=True)
Company = models.ForeignKey(company,on_delete=models.CASCADE,null=True,blank=True,related_name='Companys')
name = models.CharField(max_length=32)
group1_Name = models.ForeignKey(group1,on_delete=models.CASCADE,blank=True,null=True)
Do any one know what I am doing wrong in my code?
Thank you in advance
You can override the FormMixin.get_form_kwargs [Django-doc] in your view, that it constructs a dictionary with the parameters necessary to initialize the form, like:
class ledger1CreateView(LoginRequiredMixin,CreateView):
form_class = Ledgerform
def get_form_kwargs(self):
data = super(ledger1CreateView, self).get_form_kwargs()
data.update(
User=self.request.User,
Company=company.objects.get(pk=self.kwargs['pk'])
)
return data
The form_valid function is called after the form is constructed, validated and appears to be valid. Typically it is used to redirect the user to the "success page".

Insert One to One field value in django

I have the following models.
class PatientInfo(models.Model):
lastname = models.CharField('Last Name', max_length=200)
firstname = models.CharField('First Name',max_length=200)
middlename = models.CharField('Middle Name',max_length=200)
...
def get_absolute_url(self):
return reverse('patient:medical-add', kwargs={'pk': self.pk})
class MedicalHistory(models.Model):
patient = models.OneToOneField(PatientInfo, on_delete=models.CASCADE, primary_key=True,)
...
and upon submitting PatientInfo form it will go to another form which supply the MedicalHistory Details. I can see my PatientInfo data as well as MedicalHistory data but not linked to each other. Below is my MedicalCreateView which process my MedicalHistory form.
class MedicalCreateView(CreateView):
template_name = 'patient/medical_create.html'
model = MedicalHistory
form_class = MedicalForm
def post(self, request, pk):
form = self.form_class(request.POST)
if form.is_valid():
patiente = form.save(commit=False)
physician_name = form.cleaned_data['physician_name'] # do not delete
patient = PatientInfo.objects.all(id=self.kwargs['pk'])
MedicalHistory.patient = self.kwargs['pk']
patiente.save()
messages.success(request, "%s is added to patient list" % physician_name )
return redirect('index')
else:
print(form.errors)
This is how I set MedicalHistory.patient field using the PatientInfo.pk
MedicalHistory.patient = self.kwargs['pk']
If you are using OneToOneField and want to link MedicalHistory to PatientInfo automatically you need to use signals.
class MedicalHistory(models.Model):
patient = models.OneToOneField(PatientInfo, on_delete=models.CASCADE, primary_key=True,)
. . . . .
#receiver(post_save, sender=PatientInfo)
def create_medical_history(sender, instance, created, **kwargs):
if created:
MedicalHistory.objects.create(patient=instance)
#receiver(post_save, sender=PatientInfo)
def save_medical_history(sender, instance, **kwargs):
instance.medicalhistory.save()
Views
class MedicalCreateView(CreateView):
template_name = 'patient/medical_create.html'
model = MedicalHistory
form_class = MedicalForm
success_url = '/'

"Select a valid choice. <choice> is not one of the available choices" error when submitting ManyToMany ModelForm

I want to limit the choices of a ManyToManyField to those matching a ForeignKey. The form displays properly, but upon saving results in an error Select a valid choice. <choice> is not one of the available choices.
Before I was trying to limit the queryset by passing a parameter in the view to the form, and then using that parameter to filter the queryset.
Models:
class VenueEventTimeslot(models.Model):
venue = models.ForeignKey(Venue)
name = models.CharField(max_length=255)
class VenueEvent(models.Model):
venue = models.ForeignKey(Venue)
event_timeslots = models.ManyToManyField(VenueEventTimeslot)
class VenueEventForm(ModelForm):
event_timeslots = ModelMultipleChoiceField(queryset=None, widget=CheckboxSelectMultiple())
def __init__(self, *args, **kwargs): # limit timeslots to those of the venue only
venue_obj = kwargs.pop('venue_obj',None)
super(VenueEventForm, self).__init__(*args,**kwargs)
self.fields['event_timeslots'].queryset=VenueEventTimeslot.objects.filter(venue=venue_obj)
class Meta:
model = VenueEvent
fields = ['event_timeslots']
Views:
#login_required
def calendar(request, pk):
venue = Venue.objects.get(pk = pk)
if request.method == "POST":
form = VenueEventForm(request.POST)
if form.is_valid():
# form stuff
else:
form = VenueEventForm(venue_obj = venue)
context = {'venue':venue, 'form':form}
return render(request, ... , context)
However, if I pass the queryset from the view, it works perfectly.
Models:
class VenueEventTimeslot(models.Model):
# same as above
class VenueEvent(models.Model):
# same as above
class VenueEventForm(ModelForm):
class Meta:
model = VenueEvent
fields = ['date','client_name','event_timeslots']
widgets = {
'date': SelectDateWidget(),
'event_timeslots': CheckboxSelectMultiple(),
}
Views:
#login_required
def calendar(request, pk):
venue = Venue.objects.get(pk = pk)
if request.method == "POST":
form = VenueEventForm(request.POST)
if form.is_valid():
# form stuff
else:
form = VenueEventForm()
form.fields['event_timeslots'].queryset=VenueEventTimeslot.objects.filter(venue=venue)
context = {'venue':venue, 'form':form}
return render(request, ..., context)
Would anyone be able to shed some light on this?
I just solved a problem similar to this yesterday which is right here, How To Exclude A Value In A ModelMultipleChoiceField?, but I think the issue with your init function is the way it is formatted. Instead of venue=venue_obj, you need to change it to pk=venue_obj because it appear you are getting the pk of venue in the view instead of the venue attribute of VenueEvent , and I reformatted your form a bit to make it look cleaner.
forms.py
class VenueEventForm(ModelForm):
def __init__(self, *args, **kwargs): # limit timeslots to those of the venue only
venue_obj = kwargs.pop('venue_obj')
super(VenueEventForm, self).__init__(*args,**kwargs)
self.fields['event_timeslots'] = forms.ModelMultipleChoiceField(widget=forms.CheckboxSelectMultiple(), queryset=VenueEventTimeslot.objects.filter(pk=venue_obj))
class Meta:
model = VenueEvent
fields = ['event_timeslots']
views.py
#login_required
def calendar(request, pk):
venue = Venue.objects.get(pk = pk)
if request.method == "POST":
form = VenueEventForm(request.POST, venue_obj=venue)
if form.is_valid():
# form stuff
else:
print VenueEventForm.errors
else:
form = VenueEventForm(venue_obj=venue)
context = {'venue':venue, 'form':form}
return render(request, ... , context)

inlineformset_factory minimal required

Using inlineformset_factory I am able to add / remove phone numbers related to a single customer. Only problem is, I want to require at least 1 valid phone number for each customer.
Here is some demo code:
Models:
class Customer( models.Model ):
name = models.CharField( max_length=255 )
class PhoneNumber( models.Model ):
customer = models.ForeignKey( Customer )
number = models.CharField( max_length=10 )
Forms:
class CustomerForm( ModelForm ):
class Meta:
model = Customer
fields = ['name']
class PhoneNumberForm( ModelForm ):
class Meta:
model = PhoneNumber
fields = ['number']
Ok, so that's pretty straight forward.
Then in my view:
class Create( View ):
template_name = 'path_to_template'
CustomerForm = forms.CustomerForm
PhoneNumberFormSet = inlineformset_factory (
parent_model = Customer,
model = PhoneNumber,
form = PhoneNumberForm,
extra = 1,
)
def get(self, request):
# Return empty forms
context = {
'customer_form': self.CustomerForm,
'phone_number_formset': self.PhoneNumberFormSet
}
render( request, self.template_name, context)
def post(self, request):
this_customer_form = self.CustomerForm( request.POST )
if this_customer_form.is_valid():
new_customer.save(commit=False)
this_phone_number_formset = self.PhoneNumberFormSet(request.POST, instance=new_customer)
if this_phone_number_formset.is_valid():
new_customer.save()
this_phone_number_formset.save()
return HttpResponseRedirect(reverse_lazy('customer-detail', kwargs={'pk': new_customer.pk}))
# Something is not right, show the forms again
this_phone_number_formset = self.PhoneNumberFormSet(request.POST)
context = {
'customer_form': this_customer_form,
'phone_number_formset': this_phone_number_formset
}
render( request, self.template_name, context)
You get the point I think. Same thing for the Edit/Update view of the customer. Only then the forms are prepopulated.
At this point all I need is a way to require at least 1 valid PhoneNumber per Customer.
I found something like:
class RequiredFormSet(BaseFormSet):
def __init__(self, *args, **kwargs):
super(RequiredFormSet, self).__init__(*args, **kwargs)
for form in self.forms:
form.empty_permitted = False
from https://stackoverflow.com/questions/2406537/django-formsets-make-first-required
but it doesnt seem to work when I apply this on a BaseInlineFormSet class.
Django 1.7 seems to answer my wishes, but not for a InlineModelFormSet so far..
Any ideas?
If you just want to set the minimum or maximum, you can set them directly in inlineformset_factory, here's my code for minimum of one entry
from django.forms import inlineformset_factory
SubUnitFormSet = inlineformset_factory(
Unit, SubUnit, form=SubUnitForm, min_num=1, validate_min=True, extra=0)
You need to properly handle this in your view. I'm using CBV and this is my code for your reference
class UnitCreateView(PermissionRequiredMixin, SuccessMessageMixin, CreateView):
permission_required = "core.add_unit"
model = Unit
form_class = UnitForm
template_name = 'core/basic-info/unit_form.html'
success_url = reverse_lazy('core:units')
success_message = _("%(code)s was added successfully")
def get_context_data(self, **kwargs):
data = super(UnitCreateView, self).get_context_data(**kwargs)
if self.request.POST:
data['subunits'] = SubUnitFormSet(self.request.POST, )
else:
data['subunits'] = SubUnitFormSet()
return data
def form_valid(self, form):
context = self.get_context_data()
subunits = context['subunits']
with transaction.atomic():
if subunits.is_valid():
self.object = form.save()
subunits.instance = self.object
subunits.save()
else:
return self.render_to_response(self.get_context_data(form=form))
return super(UnitCreateView, self).form_valid(form)
Thank you kezabella ( django irc ).
Seems I found a solution by subclassing BaseInlineFormset:
class RequiredFormSet(BaseInlineFormSet):
def clean(self):
for form in self.initial_forms:
if not form.is_valid() or not (self.can_delete and form.cleaned_data.get('DELETE')):
return
for form in self.extra_forms:
if form.has_changed():
return
raise ValidationError("No initial or changed extra forms")
Btw, these validation errors do not show up in {{ formset.error }} but in:
{{ formset.non_form_errors }}

django form set current login user

#login_required
def post_review(request):
if request.method == 'POST':
formset = ReviewForm(request.POST)
if formset.is_valid():
formset.save(commit=False)
#formset.author = User.objects.get(pk=int(request.user.id))
formset.pub_date = datetime.datetime.now
formset.save()
return HttpResponseRedirect(reverse(review_index))
else:
formset = ReviewForm()
return render_to_response("review/post_review.html",
{"formset": formset}, context_instance=RequestContext(request),
)
I have this view, I want to auto set the current logged-in user in my review form author field. But I dont know how. Any ideas/hint pls?
Below is my form:
class ReviewForm(ModelForm):
class Meta:
model = Review
fields = ('title','category', 'body', )
widgets = {
'body': Textarea(attrs={'cols': 60, 'rows': 20}),
}
I've always done this by accepting a new kwarg in my form's __init__, and saving the value until save-time.
class ReviewForm(ModelForm):
class Meta:
model = Review
fields = ('title','category', 'body', )
widgets = {
'body': Textarea(attrs={'cols': 60, 'rows': 20}),
}
def __init__(self, *args, **kwargs):
self._user = kwargs.pop('user')
super(ReviewForm, self).__init__(*args, **kwargs)
def save(self, commit=True):
inst = super(ReviewForm, self).save(commit=False)
inst.author = self._user
if commit:
inst.save()
self.save_m2m()
return inst
And then in my view:
def post_review(request):
# ... snip ...
if request.method == 'POST'
form = ReviewForm(request.POST, user=request.user)
if form.is_valid():
form.save()
return HttpResponseRedirect('/thanks/') #or whatever the url
else:
# Don't forget to add user argument
form = ReviewForm(user=request.user)
# ... snip ...
If Review.author isn't a required field, you can add a second value to the kwargs.pop call to set a default, like None. Otherwise, if the user kwarg isn't provided, it'll raise an error, effectively making it a required argument.
As an alternative solution, in Django 2+ using a form view - such as a CreateView or FormView, I can simply pass the self.request.user to my pre-saved form model:
class AppCreateView(CreateView):
model = models.App
fields = ['name']
success_url = '/thanks/'
def form_valid(self, form):
app_model = form.save(commit=False)
app_model.author = self.request.user
# app_model.user = User.objects.get(user=self.request.user) # Or explicit model
app_model.save()
return super().form_valid(form)
I agree the class based view is not important here. The important line is app_model.author = self.request.user.
The model is not special:
from django.db import models
from django.contrib.auth.models import User
class App(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
name = models.CharField(max_length=255, help_text="Arbitrary name")
created = models.DateTimeField(auto_now_add=True, max_length=255)
I have a formset mixin which lets you pass extra arguments to the generated forms.
Just add the mixin as the first base class, set a dictionary named "form_kwargs" as a class attribute to describe the
arguments to pass.
from django.forms.formsets import BaseFormSet
class BaseKwargsFormSet(BaseFormSet):
"""
A formset mix-in to allow keyword arguments to be passed to constructed forms
For model_formsets, derive from this model *first* because django's formsets
can't grok the extra arguments.
To use, specify a dictionary with the kwargs & default values as an attribute
named "form_kwargs" on the formset base class.
example:
class BaseUserModelFormset (BaseKwargsFormSet, BaseModelFormSet):
form_kwargs = { 'user': None }
UserFormset = modelformset_factory (usermodel, form=userform,
formset=BaseUserModelFormset)
formset = UserFormset (request.POST or None, user=request.user)
"""
def __init__(self, *args, **kwargs):
form_kwargs = getattr(self, 'form_kwargs', {})
self.form_kwargs = dict((k, kwargs.pop(k, v)) for k, v in form_kwargs.items())
super(BaseKwargsFormSet, self).__init__(*args, **kwargs)
def _construct_form(self, index, **kwargs):
kwargs.update(**self.form_kwargs)
return super(BaseKwargsFormSet, self)._construct_form(index, **kwargs)