Saving the form while there are two ForeignKeys, - django

I have two models as shown below
class college(models.Model):
name = models.CharField(max_length=256)
class education(models.Model):
author = models.ForeignKey('auth.User')
school = ForeignKey(college)
field = models.CharField(max_length=200)
startyear = models.IntegerField(blank =True,null = True)
endyear = models.IntegerField(blank =True,null = True)
Views as shown below
class EducationListView(ListView):
template_name = 'education.html'
def get_queryset(self):
return education.objects.filter(author__username=self.request.user.username).order_by('-startyear')
class EducationCreate(CreateView):
model = dupeducation
fields = ('school','field','startyear','endyear')
template_name = 'education_form.html'
def form_valid(self, form):
form.instance.author = self.request.user
obj,created = college.objects.get_or_create(name=form['school'])
obj.save()
form.instance.school = obj
return super(EducationCreate, self).form_valid(form)
class EducationUpdate(UpdateView):
model = education
fields = ('school','field','startyear','endyear')
template_name = 'education_form.html'
def form_valid(self, form):
form.instance.author = self.request.user
return super(EducationUpdate, self).form_valid(form)
class EducationDelete(DeleteView):
model = education
success_url = reverse_lazy('education')
I am unable to save the form. It's throwing an error to the school field like this "Select a valid choice. That choice is not one of the available choices.".
My goal is to take input for the school field and check that field with get_object_or_create . If that object does not exist, create it and attach it to the school field.

If you debug, you'll see that save() is not being reached. Your problem is in the Field validation.
What you need to do is to override the clean_<field>() method called before any object is saved.
You can read more about it here: Django how to override clean() method in a subclass of custom form?
While overriding clean_school(), you will be able to add the value to the database, and later, in save(), simply make the attribution.

Related

Create form to change relationship from related model's form

I have two models:
class Thing(forms.ModelForm):
class Owner(forms.ModelForm):
thing = models.OneToOneField(Thing)
I want to add a form to change the owner in Thing's UpdateView. I can do it like this:
class ThingForm(forms.ModelForm):
owner = forms.ModelChoiceField(
queryset=Owner.objects.all(),
)
class Meta:
model = Thing
fields = '__all__'
And then process the result inside form_valid() method. But isn't there a more direct approach for this, where i just add this to the fields of the form?
UPDATE
So I ended up doing it like this:
class ThingUpdateView(UpdateView):
model = Thing
form_class = ThingForm
def get_initial(self):
initial = super(ThingUpdateView, self).get_initial()
try:
initial['owner'] = self.object.owner
except Thing.owner.RelatedObjectDoesNotExist:
pass
return initial
def form_valid(self, form):
self.object = form.save(commit=False)
owner = form.cleaned_data['owner']
owner.thing = self.object
owner.save(update_fields=['thing'])
self.object.save()
return redirect(self.object.get_absolute_url())
Maybe there's a better way.

TypeError: super(type, obj): obj must be an instance or subtype of type?

Why am I getting this error?
TypeError: super(type, obj): obj must be an instance or subtype of type
This is my models.py file
class UserNotification(models.Model):
Name = models.CharField(max_length=250)
Mobile_No = models.CharField(max_length=10, validators=[RegexValidator(r'^\d{1,10}$')])
Proof = models.TextField()
viewed = models.BooleanField(default=False)
user = models.ForeignKey(User)
date = models.DateTimeField(default=timezone.now)
def __str__(self):
return self.Name
class Meta:
ordering = ["-date"]
This is my views.py file
class RequestItem(generic.CreateView):
model = UserNotification
fields = ['Name', 'Mobile_No', 'Proof']
def get_form(self, form_class=None):
if form_class is None:
form_class = self.get_form_class()
form = super(UserNotification, self).get_form(form_class)
form.fields['Name'].widget = TextInput(attrs={'placeholder': '*Enter your name'})
form.fields['Mobile_No'].widget = TextInput(
attrs={'placeholder': "*Enter your's mobile number to get a call back from angel"})
form.fields['Proof'].widget = TextInput(attrs={'placeholder': '*enter proof you have for your lost item'})
return form
def form_valid(self, form):
print(self.kwargs)
self.object = form.save(commit=False)
qs = Report_item.objects.filter(id=self.kwargs.get("pk"))
self.object.user = qs[0].owner
self.object.save()
return HttpResponse("<h1>Your request has been processed</h1>")
I am using django 1.11. There was no error and code working properly until I add the placeholder function. After adding the placeholder I am getting this error. Please help me to resolve it.
The problem is where you call super() inside get_form. You need to use the current class; for some reason you have put the model class there. It needs to be:
form = super(RequestItem, self).get_form(form_class)
Or better, since you are using Python 3, use the short version:
form = super().get_form(form_class)
Note however this isn't really a good way to do what you're trying to do here. Rather, declare an actual form class which sets the widget attributes for the fields you want to change, and refer to it in the view class by setting the form_class attribute at class level.

How to limit field choices related to another table in database?

I'm working on simple expenses manager. Every user is able to add/remove/edit an Operation, which represents expense or earning. However I have noticed a bug - while adding new Operation it is possible to choose other users Accounts and Categories.
Here is my Operation model:
class Operation(models.Model):
def get_category_color(self):
return Category.objects.get(name=self.category).color
types_tuples = ((-1, 'expense'), (1, 'earning'))
user = models.ForeignKey(User)
account = models.ForeignKey(Account)
category = models.ForeignKey(Category)
date = models.DateField()
amount = models.DecimalField(max_digits=10, decimal_places=2)
type = models.IntegerField(choices=types_tuples)
currency = models.CharField(max_length=3)
color = property(get_category_color)
OperationCreate view:
class OperationCreate(CreateView, OperationMixIn):
model = Operation
form_class = OperationForm
success_url = reverse_lazy('manage_operations')
def form_valid(self, form):
operation = form.save(commit=False)
operation.currency = Account.objects.get(pk=form.instance.account_id).currency
self.update_account_balance(form)
form.instance.user = self.request.user
return super(OperationCreate, self).form_valid(form)
and OperationForm:
class OperationForm(ModelForm):
class Meta:
model = Operation
fields = ['account', 'type', 'category', 'date', 'amount']
The question is how can I limit choices for Account and Category that are available during posting new Operation? I would like user to see only those which are related to his/her account.
I tried limit_choices_to as a parameter for models.ForeignKey(Account) and models.ForeignKey(Category) in Operation model but couldn't make it work that way.
I assume I need to use a query which will return only Accounts and Categories related to the current user. However I have no clue how and where should I apply it.
Could you please point me in the right direction?
EDIT:
OperationForm edited due to #efkin suggestion:
class OperationForm(ModelForm):
class Meta:
model = Operation
fields = ['account', 'type', 'category', 'date', 'amount']
def __ini__(self, user, *args, **kwargs):
super(OperationForm, self).__init__(*args, **kwargs)
self.fields['account'] = ModelChoiceField(queryset=Account.objects.filter(user=user))
self.fields['category'] = ModelChoiceField(queryset=Category.objects.filter(user=user))
You could use a simple Form with ModelChoiceField fields for foreign keys.
Okay, so I did some more digging and came up with the solution. OperationForm must be modified using ModelChoiceFiled (as #efkin said) and OperationCreate should contains override for get_form_kwargs method from ModelFormMixIn:
class OperationCreate(CreateView, OperationMixIn):
model = Operation
form_class = OperationForm
success_url = reverse_lazy('manage_operations')
def get_form_kwargs(self):
kwargs = super(OperationCreate, self).get_form_kwargs()
kwargs.update({'user': self.request.user})
return kwargs
def form_valid(self, form):
operation = form.save(commit=False)
operation.currency = Account.objects.get(pk=form.instance.account_id).currency
self.update_account_balance(form)
form.instance.user = self.request.user
return super(OperationCreate, self).form_valid(form)

Django: validating unique_together constraints in a ModelForm with excluded fields

I have a form:
class CourseStudentForm(forms.ModelForm):
class Meta:
model = CourseStudent
exclude = ['user']
for a model with some complicated requirements:
class CourseStudent(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
semester = models.ForeignKey(Semester)
block = models.ForeignKey(Block)
course = models.ForeignKey(Course)
grade = models.PositiveIntegerField()
class Meta:
unique_together = (
('semester', 'block', 'user'),
('user','course','grade'),
)
I want the new object to use the current logged in user for CourseStudent.user:
class CourseStudentCreate(CreateView):
model = CourseStudent
form_class = CourseStudentForm
success_url = reverse_lazy('quests:quests')
def form_valid(self, form):
form.instance.user = self.request.user
return super(CourseStudentCreate, self).form_valid(form)
This works, however, because the user is not part of the form, it misses the validation that Django would otherwise do with the unique_together constraints.
How can I get my form and view to use Django's validation on these constraints rather than having to write my own?
I though of passing the user in a hidden field in the form (rather than exclude it), but that appears to be unsafe (i.e. the user value could be changed)?
Setting form.instance.user in form_valid is too late, because the form has already been validated by then. Since that's the only custom thing your form_valid method does, you should remove it.
You could override get_form_kwargs, and pass in a CourseStudent instance with the user already set:
class CourseStudentCreate(CreateView):
model = CourseStudent
form_class = CourseStudentForm
success_url = reverse_lazy('quests:quests')
def get_form_kwargs(self):
kwargs = super(CreateView, self).get_form_kwargs()
kwargs['instance'] = CourseStudent(user=self.request.user)
return kwargs
That isn't enough to make it work, because the form validation skips the unique together constraints that refer to the user field. The solution is to override the model form's full_clean() method, and explicitly call validate_unique() on the model. Overriding the clean method (as you would normally do) doesn't work, because the instance hasn't been populated with values from the form at that point.
class CourseStudentForm(forms.ModelForm):
class Meta:
model = CourseStudent
exclude = ['user']
def full_clean(self):
super(CourseStudentForm, self).full_clean()
try:
self.instance.validate_unique()
except forms.ValidationError as e:
self._update_errors(e)
This worked for me, please check. Requesting feedback/suggestions.
(Based on this SO post.)
1) Modify POST request to send the excluded_field.
def post(self, request, *args, **kwargs):
obj = get_object_or_404(Model, id=id)
request.POST = request.POST.copy()
request.POST['excluded_field'] = obj
return super(Model, self).post(request, *args, **kwargs)
2) Update form's clean method with the required validation
def clean(self):
cleaned_data = self.cleaned_data
product = cleaned_data.get('included_field')
component = self.data['excluded_field']
if Model.objects.filter(included_field=included_field, excluded_field=excluded_field).count() > 0:
del cleaned_data['included_field']
self.add_error('included_field', 'Combination already exists.')
return cleaned_data

Save a django object getting another model instance as foreign key from a form

I'm stuck trying to save an instance of a model that gets from a form an instance of another model as foreign key.
Models
class Customer(models.Model):
owner = models.ForeignKey(User)
custname = models.CharField()
class Appointment(models.Model):
user = models.ForeignKey(User)
start = models.DateTimeField()
end = models.DateTimeField()
customer = models.ForeignKey(Customer)
Form
class AppointmentForm(forms.Form):
basedate = forms.DateField()
start = forms.TimeField(widget=forms.Select())
end = forms.IntegerField(widget=forms.Select())
customer = forms.ModelMultipleChoiceField(queryset=Customer.objects.all())
The method that I'm not able to get working in a generic FormView:
def form_valid(self, form):
if form.is_valid():
appointment = Appointment()
appointment.user = self.request.user
basedate = form.cleaned_data['basedate']
start = form.cleaned_data['start']
duration = form.cleaned_data['end']
appointment.start = datetime.datetime.combine(basedate, start)
appointment.end = appointment.start + datetime.timedelta(minutes=duration)
appointment.save()
return super(AppointmentCreate, self).form_valid(form)
What should I add in the last method to read the foreign key customer from the form, and therefore pass it to the appointment? And is there any way of filtering so that in the form only appear customers belonging to the request.user?
Many thanks in advance for your help.
Something like this should work. A couple of things:
1) I changed the form field to a ModelChoiceField instead of multiple choice. You'll want to use a ModelChoiceField to show the relationship. I changed this from MultipleChoice since, according to your model, you only want to save one choice. You can read more on ModelChoiceFields here: https://docs.djangoproject.com/en/dev/ref/forms/fields/
2) In your forms, I changed the choice query to customer = forms.ModelChoiceField(queryset=Customer.objects.filter(owner=request.user). This will filter for Customers of the specific user only.
forms.py
class AppointmentForm(forms.Form):
def __init__(self, *args, **kwargs):
self.request = kwargs.pop("request")
super(AppointmentForm, self).__init__(*args, **kwargs)
basedate = forms.DateField()
start = forms.TimeField(widget=forms.Select())
end = forms.IntegerField(widget=forms.Select())
customer = forms.ModelChoiceField(queryset=Customer.objects.filter(owner=request.user))
views.py
def form_valid(self, form):
if request.method=='POST':
form = AppointmentForm(request.POST, request=request)
if form.is_valid():
appointment = Appointment()
appointment.user = self.request.user
basedate = form.cleaned_data['basedate']
start = form.cleaned_data['start']
duration = form.cleaned_data['end']
appointment.customer = form.cleaned_data['customer']
appointment.start = datetime.datetime.combine(basedate, start)
appointment.end = appointment.start + datetime.timedelta(minutes=duration)
appointment.save()
return super(AppointmentCreate, self).form_valid(form)
else:
form = AppointmentForm()
Finally I did it. The key was to override the get method of FormView class in views.py, rather than modifying the init in forms.py:
forms.py:
class AppointmentForm(forms.Form):
basedate = forms.DateField()
start = forms.TimeField(widget=forms.Select())
end = forms.IntegerField(widget=forms.Select())
customer = forms.ModelChoiceField(queryset=Customer.objects.all())
...
views.py:
def get(self, request, *args, **kwargs):
"""
Handles GET requests and instantiates a blank version of the form.
"""
choices_start, choices_duration = self._get_choices()
form_class = self.get_form_class()
form = self.get_form(form_class)
form.fields['start'].widget=forms.Select(choices=choices_start)
form.fields['end'].widget=forms.Select(choices=choices_duration)
form.fields['customer'].queryset=Customer.objects.filter(owner=request.user)
return self.render_to_response(self.get_context_data(form=form))
#Dan: Many thanks for your effort in helping me out.