Validate POST data with extra argument using Django modelForm - python

I have this model:
class SearchPreference(models.Model):
"""Saves the preferred location and school_type of the User
"""
user = models.OneToOneField(User, related_name='search_preference')
location = models.ForeignKey(Location, null=True)
school_type = models.ForeignKey(SchoolType, null=True)
class Meta:
app_label = 'grants'
and this form:
class SearchPreferenceForm(forms.ModelForm):
location = forms.ChoiceField(queryset=Location.objects.all(),
to_field_name='slug',
required=False)
school_type = forms.ChoiceField(queryset=SchoolType.objects.all(),
to_field_name='slug',
required=False)
class Meta:
model = SearchPreference
fields = ('location', 'school_type')
I am trying to use the form to do validation of POST data, I am not displaying it in a template.
The problem is, the POST data can include a value which isn't in the Location or SchoolType table, so the form doesn't validate. The value is 'all', signifying 'all locations' or 'all school types', and I really want this to be saved as a SearchPreference with no location, i.e. location = null.
I could change 'all' to an empty value and that might work but then validation/logic has moved out of the form.
I thought I could use empty_value = 'all' but this doesn't work on a modelChoiceField.
Is there any way of doing this?

Your model needs blank=True as well and null=True
location = models.ForeignKey(Location, blank=True, null=True)
school_type = models.ForeignKey(SchoolType, blank=True, null=True)
This post talks about blank and null.

This worked in the end:
class SearchPreferenceForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(SearchPreferenceForm, self).__init__(*args, **kwargs)
self.fields['location'].empty_values.append('all')
self.fields['school_type'].empty_values.append('all')

Related

How to pre fill fields in a form when using django-bootstrap-modal-forms

I am using django-bootstrap-modal-forms and it works perfectly as in documentation when using fields form my model. Some of the fields are ForeignKeys and they are displayed properly for user to select a value from database table that is referenced by the key, but instead of that I need to put username of the current user.
I tried to change how the CreateView class handles fields, but with no luck. Probably doing something wrong.
models.py
class userSchoolYear(models.Model):
user_in_school = models.ForeignKey(get_user_model(), null=True, on_delete=models.CASCADE)
school = models.ForeignKey(sifMusicSchool, on_delete=models.CASCADE)
school_year = models.ForeignKey(sifSchoolYear, on_delete=models.CASCADE)
school_year_grade = models.CharField(max_length=4, choices=IZBOR_RAZREDA, default=None, null=True)
user_instrument = models.ForeignKey(instType, on_delete=models.CASCADE, default=None, null=True)
user_profesor = models.ForeignKey(profSchool, on_delete=models.CASCADE, default=None, null=True)
views.py
class SchoolYearCreateView(BSModalCreateView):
template_name = 'school_year.html'
form_class = SchoolYearForm
success_message = 'Success!'
success_url = reverse_lazy('school')
def __init__(self, *args, **kwargs):
self.form_class.user_in_school = 'johnny' ### HERE
print(user.username)
super().__init__(*args, **kwargs)
forms.py
class SchoolYearForm(BSModalForm):
class Meta:
model = userSchoolYear
fields = '__all__'
Thanks to the author Uroš Trstenjak I was able to find a solution. I was wrong trying to set field values from views.py, instead it should be done in forms.py. So, basically I had to write a init for the form and alter fields values. Uroš pointed out that at from level I can get current user from self.request.user and it did work.

Update records depending on fields - Django

I want to override the save method of a model in Django.
Consider this model:
class OtherClass(models.Model):
name = models.CharField(max_length=255, blank=True, null=True)
class MyClass(models.Model):
admin = models.ForeignKey(OtherClass, on_delete=models.CASCADE)
myfield1 = models.CharField(max_length=128)
myfield2 = models.CharField(max_length=128)
myfield3 = models.CharField(max_length=128, blank=True, null=True)
myfield4 = models.CharField(max_length=128, blank=True, null=True)
myfield5 = models.TextField(blank=True)
What I want, is to update the model only if myfield5 is present in whatever form or view the user is filling. This is the current save method I have:
def save(*args, **kwargs):
fieldgroup = f"{self.myfield2}\n{self.myfield3}"
self.myfield5 = f"{fieldgroup}\n{self.field1} {self.field4} {self.field5}"
super().save(*args, **kwargs)
Any ideas?
Maybe it would be better to achieve this on a form rather than directly into the model?
You can check self.pk to determine whether the object was already previously saved or not (the first time self.pk is None).
So in combination with self.field5, you can do this:
if self.pk and self.field5:
self.field5 = ... # update field5 as you need
super().save(...)
elif not self.pk:
super().save(...)
# no saving if self.pk and not self.fields
You can use required=True in your Model field or Form field

Multiline Charfield Django

How can I make a multi-line CharField in a model form in Django? I don't want to use Textarea, as I want the input to be with limited length. The question is about the field 'description'
This is my model:
class Resource(models.Model):
type = models.CharField(max_length=50)
name = models.CharField(max_length=150)
description = models.CharField(max_length=250, blank=True)
creationDate = models.DateTimeField(default=datetime.now, blank=True)
And this is my form:
class AddNewResourceForm(forms.ModelForm):
class Meta:
model = Resource
fields = ("name","type","description")
def __init__(self, *args, **kwargs):
super(AddNewResourceForm, self).__init__(*args, **kwargs)
self.fields['name'].widget.attrs.update({'class' : 'new-res-name',
'placeholder' : 'max 150 characters'})
self.fields['type'].widget.attrs.update({'class' : 'new-res-type',
'placeholder' : 'max 50 characters'})
self.fields['description'].widget.attrs.update({'class' : 'new-res-
description', 'placeholder' : 'max 250 characters'})
I think that you should use the TextField, making sure to enforce the desired limit, which can be done in 2 steps:
1) Set a max_length attribute on the description field which will make sure that the limit is reflected on the client side.
From the docs:
If you specify a max_length attribute, it will be reflected in the
Textarea widget of the auto-generated form field. However it is not
enforced at the model or database level.
2) Apply MaxLengthValidator to your field to make sure you have a server-side limit validation as well, e.g.
from django.core.validators import MaxLengthValidator
class Resource(models.Model):
type = models.CharField(max_length=50)
name = models.CharField(max_length=150)
description = models.TextField(max_length=250, blank=True,
validators=[MaxLengthValidator(250)])
creationDate = models.DateTimeField(default=datetime.now, blank=True)

Django-Rest-Framework, POST operation: fields is not empty but DRF says is required

Well, i have this model:
class Application(models.Model):
name = models.CharField("nom", unique=True, max_length=255)
sonarQube_URL = models.CharField("Url SonarQube", max_length=255,
blank=True, null=True)
def __unicode__(self):
return self.name
and this serializer:
class ApplicationSerializer(serializers.ModelSerializer):
nom = serializers.CharField(source='name', required=True, allow_blank=True)
url_sonarqube = serializers.CharField(source='sonarQube_URL', required=False)
flows = FlowSerializer(many=True, read_only=True)
class Meta:
model = Application
fields = ('id', 'nom', 'url_sonarqube', 'flows')
My view is simple:
class ApplicationViewSet(viewsets.ModelViewSet):
queryset = Application.objects.all()
serializer_class = ApplicationSerializer
I use this model of permissions in my settings.py:
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.DjangoModelPermissions',),
'PAGE_SIZE': 10,
'TEST_REQUEST_DEFAULT_FORMAT': 'json',
'TEST_REQUEST_RENDERER_CLASSES': (
'rest_framework.renderers.MultiPartRenderer',
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.TemplateHTMLRenderer'
)
}
When I use POST operation on DRF interface (in HTML Form), I filled all the fields of the application Item. As you can see, "required" parameter of "nom" is set to True. And, this is the problem: even if 'nom' is not empty, DRF says "this field is required!". So, I can't POST a new application item.
I don't understand why it not works... Where is the mistake?
The error you got is related to the Django Model(Application). It was failing in model level not in serializer level. Add null=True and blank=True to name field in Application model.
Try to keep your code in English, you can use Django's i18n to translate stuff, also use blank and null for your name field:
class Application(models.Model):
name = models.CharField(_('name'), max_length=255, blank=True, null=True)
sonarqube_url = models.CharField(_('SonarQube URL'), max_length=255, blank=True, null=True)
def __unicode__(self):
return self.name
class ApplicationSerializer(serializers.ModelSerializer):
class Meta:
model = Application
fields = ('id', 'name', 'sonarqube_url')
Let's save flows for later as you don't even have a model relation for them.

Error in saving multiple m2m field with django modelform

I have a model named Project containing three ManyToManyFields (member_student, supervisor and tag). One (tag) of which is excluded in the form and it has to be saved manually. So, in the view, I use save(commit=False) because I have to change some fields of the form. After changing the fields, the form is saved and I add the tags one by one. Then, when I call save_m2m to save ManyToManyField, I get the error given by the save_m2m line in views:
invalid literal for int() with base 10: 'a'
Here is my model.
class Tag(models.Model):
name = models.CharField(unique=True, max_length=60)
slug = models.SlugField(max_length=60, unique=True)
def save(self, *args, **kwargs):
if not self.id:
self.slug = slugify(self.name)
super(Tag, self).save(*args, **kwargs)
class Project(models.Model):
'''Main Project uploading'''
title = models.CharField(max_length=300)
description = models.TextField(null=True)
#year = models.ForeignKey(Year)
tag = models.ManyToManyField(Tag)
owner_student = models.ForeignKey(Student, related_name='member_student')
member_student = models.ManyToManyField(Student, blank=True, null=True)
supervisor = models.ManyToManyField(Supervisor, blank=True, null=True)
subject = models.ForeignKey(Subject)
main_document = models.FileField(upload_to='main_documents/')
supporting_document = models.FileField(upload_to='supp_documents/', blank=True, null=True)
source_code = models.FileField(upload_to='source_code/', blank=True, null=True)
screenshot = models.ImageField(upload_to='screenshots/', blank=True, null=True)
This is the forms.py:
class ProjectForm(forms.ModelForm):
class Meta:
model = Project
exclude = ['owner_student', 'slug', 'tag']
tag = forms.CharField(max_length=100)
Here is the View.
def add_project(request):
parameters = {}
if request.method =="POST":
upload_form = ProjectForm(request.POST, request.FILES)
if upload_form.is_valid():
new_form = upload_form.save(commit=False)
mystud = Student.objects.get(user=request.user)
new_form.owner_student = mystud
new_form.save()
tags = upload_form.cleaned_data['tag']
tags = tags.split(',')
for eachtag in tags:
tag, created = Tag.objects.get_or_create(name=eachtag.strip())
tag.save()
new_form.tag.add(tag)
upload_form.save_m2m()
return HttpResponseRedirect(reverse(project_page, args=(new_form.slug,)))
else:
parameters["upload_form"] = upload_form
return render_to_response('upload.html', parameters)
else:
upload_form = ProjectForm()
parameters["upload_form"] = upload_form
parameters["page_title"] = "Upload your Project"
return render_to_response('upload.html', parameters)
So, my question is how can I save the tags as well as the two other ManyToManyField without getting error ? I guess the save_m2m function is giving error because of the tuple returned by get_or_create.
Don't use 'tag' as the name of the charfield on your form. That'll cause save_m2m to think it needs to use the values in the charfield to set the related 'tag' field on the object.
Internally, save_m2m goes through each many-to-many field in the model. It checks for the presence of data under that name in the form's cleaned_data dictionary, and if present has the model field object update record the contents using the field's save_form_data method. It trusts the form field to have returned the right type of Python object. In this case, your charfield is returning a string (as expected), but it's incorrect to assign a string to a many-to-many field.

Categories