Forms fields respecting DRY django - django

Given some forms in Django (take the following for simplicity)
class LoginRegisterForm(forms.Form):
email = forms.EmailField(label="Email", max_length=100)
password = forms.CharField(label="Password", widget=forms.PasswordInput(), max_length=100)
We're trying to restrict the submission of these forms if there are additional fields submitted so we have the following added method to our form class
def correct_fields(self):
return set(self.data.keys()) == {"Email", "Password"}
And the following code in the views.py method corresponding to the submission of this form:
if form.is_valid() and form.correct_fields:
How can we avoid having to write Email and Password in both the definition of the form and the correct_fields method? Does django offer a build-in function to prevent forms being submitted if the correct fields are not submitted? (The form is still submitted if the correct fields and some additional fields are given). If this functionality is not given, how do I avoid writing the fields twice?

Fields where you do not specify required=False are required. As the documentation on the required=… parameter [Django-doc] says:
By default, each Field class assumes the value is required, so if you pass an empty value – either None or the empty string ("") – then clean() will raise a ValidationError exception.
So it will already validate that data indeed contains email and password. You can define optional fields with:
class LoginRegisterForm(forms.Form):
email = forms.EmailField(label="Email", max_length=100)
password = forms.CharField(label="Password", widget=forms.PasswordInput(), max_length=100)
info = forms.CharField(label="Tell us someting", required=False, intial='')

Related

DRF: Best way to supply a dynamic default field value?

Our SAAS site utilizes a DRF backend with a Vue frontend. We have fields that do not require a value from the user, but do require a value in the database. I'd like to know where's the best place to supply such dynamic defaults. I've read in other posts that "save() is not always called" - though I don't yet know the circumstances where it would not be called.
So, consider the following model:
class Tenant(models.Model):
name = models.CharField(max_length=100)
subdomain = models.CharField(max_length=100, blank=True, null=True)
schema_name = models.CharField(max_length=63, unique=True)
In this case, only "name" is required (from the user); "schema_name", if left blank in the frontend form, can be derived from "name" (converting it to lowercase). Likewise, "subdomain" can be derived from "schema_name". "subdomain" can be blank/null because the "public" schema doesn't reference a subdomain, but its value will be required for all tenants other than "public".)
So where should I put the code that populates those fields if they are blank when it comes time to create or update a Tenant?
Save will be called unless you do bulk updates, so you can put it there just fine. I prefer not to if there is a choice, but sometimes there isn't.
If you want to put it in the serializer, you can write something like this, and then use a ModelViewSet to handle the details:
class TenantSerializer(ModelSerializer):
name = CharField(required=True, min_length=1)
sub_domain = CharField(required=False)
class Meta:
model = Tenant
fields = ['id', 'name', 'sub_domain']
def validate(self, attrs):
# attrs is all fields parsed & validated on a per-field level
# in here you can do validation that depends on >1 field
# values returned will be passed to the serializer create()/update()
# via the common serializer.save() method
if self.instance:
# doing an update, maybe different logic, or just ignore?
else:
if not attrs.get('sub_domain'): # missing or blank
attrs['sub_domain'] = parse_subdomain(attrs.get('name'))
return attrs

Force model fields in Django

When creating a model in Django like this example:
class Musician(models.Model):
first_name = models.CharField(max_length=50, primary_key=True)
last_name = models.CharField(max_length=50)
instrument = models.CharField(max_length=100)
I noticed some problem (not sure if that's best word) with this approach. There is nothing preventing you from creating something like:
musician = Musician()
musician.save()
effectively having primary_key value equal to None. I would like to force user to set first_name, but frankly speaking I cannot find any simple solution for that.
Is there a way to achieve this?
First of all, don't set first_name as primary key. Just leave the default primary key as the id field. A primary key needs to be unique (a first_name isn't) and should not be something a user enters.
Second, it's true that you cannot enforce a CharField to not be empty at the database level. But you can enforce it at the code level, so that anytime you create a Django Form and validate it, it will raise an error.
In fact, Django does it automatically for you, in your case. By default first_name is a required field, since you didn't set blank=True.
So if you do:
musician = Musician()
musician.full_clean()
this raises a ValidationError.
If you create a form for your model (which is what you need if you want users to create a Musician):
class MusicianForm(ModelForm):
class Meta:
model = Musician
fields = '__all__'
form = MusicianForm(data={})
form.instance.first_name
# ''
form.is_valid()
# False
form.save()
# ValueError: The Musician could not be created because the data didn't validate.
You'll also see that if you register Musician in admin.py for django admin site, you can't leave any of the fields empty. It just won't save.

Django single model multiple form

I have a single table say 'push_message' to send push messages to multiple device type i.e STB,Andriod, Feature phone. I create a single model for this. But different fields are mandatory for different device type.
Is there any way I may use same model with different form(Suppose STB & Android)
class PushNotification(models.Model):
id = models.AutoField(primary_key=True)
sched_at = models.DateTimeField(default=timezone.now)
message = models.TextField(max_length=500)
alert_ty = models.CharField(max_length=64, choices=Options.alert_type())
title = models.CharField(max_length=127)
device_ty = models.CharField(max_length=24, choices=Options.get_devices())
Based on device type few fields are may mandatory. So I want to make form based on device chosen by user.
Please provide your input how may I achieve this in Django 2.1.
Thanks in Advance.
Model is strictly representing a database in django. So having some fields "optionally" required wont work. I suggest creating them as "not required" in the db and then define them as required (required = True) in form:
class SomeCustomForm(DefaultForm):
form_field = forms.BooleanField( label='Very important field'),
required=True,
error_messages={'required':'Can`t proceed without this'})
If you want a dropdown with a list of devices and validate the form depending on it you might want to use custom clean() as per documentation (https://docs.djangoproject.com/en/2.2/ref/forms/validation/):
The form subclass’s clean() method can perform validation that requires access to multiple form fields. This is where you might put in checks such as “if field A is supplied, field B must contain a valid email address”. This method can return a completely different dictionary if it wishes, which will be used as the cleaned_data.
from django import forms
class ContactForm(forms.Form):
def clean(self):
cleaned_data = super().clean()
cc_myself = cleaned_data.get("cc_myself")
subject = cleaned_data.get("subject")
if cc_myself and subject:
# Only do something if both fields are valid so far.
if "help" not in subject:
raise forms.ValidationError(
"Did not send for 'help' in the subject despite "
"CC'ing yourself."
)

default value for ChoiceField in modelform

I have problem with django:
models.py:
SUSPEND_TIME = (
('0', '0'),
('10', '10'),
('15', '15'),
('20', '20'),
class Order(models.Model):
user = models.ForeignKey(User)
city = models.CharField(max_length=20)
...
processed = models.BooleanField(default=False)
suspend_time = models.CharField(max_length=2, choices=SUSPEND_TIME, default='0')
..
form.py:
class OrderForm(forms.ModelForm):
class Meta:
model = Order
fields = ('suspend_time', 'processed')
view.py:
try:
order = Order.objects.get(id=order_id)
except Order.DoesNotExist:
order = None
else:
form = OrderForm(request.POST, instance=order)
if form.is_valid():
form.save()
....
then I send ajax request to update instance with only "processed" param..
form.is_valid is always False if I don't send "suspend_time" !
if request contain {'suspend_time': 'some_value' ...} form.is_valid is True
I don't understand why ? suspend_time has default value.. and order.suspend_time always has some value: default or other from choices.
why after form = OrderForm(request.POST, instance=order) form['suspend_time'].value() is None, other fields (city, processed) has normal value .
The behavior is as expected. The form should validate with given data. i.e. Whatever required fields are defined in the form, should be present in the data dictionary to instantiate it.
It will not use data from instance to populate fields that are not provided in form data.
Text from django model forms
If you’re building a database-driven app, chances are you’ll have forms that map closely to Django models. For instance, you might have a BlogComment model, and you want to create a form that lets people submit comments. In this case, it would be redundant to define the field types in your form, because you’ve already defined the fields in your model.
For this reason, Django provides a helper class that let you create a Form class from a Django model.

Does Django ModelForm set unspecified fields to empty?

I have a Model that looks like:
class MyModel(Model):
name = models.TextField(blank=True, default='')
bio = models.TextField(blank=True, default='')
and a ModelForm that looks like:
class MyModelForm(ModelForm):
class Meta:
model = MyModel
fields = ('name', 'bio')
When I create/ init my form like this:
form = MyModelForm(instance=my_model, data={'bio': 'this is me'}) # where my_model has a name already set
then:
form.is_valid() # evaluates to true
form.save() # overwrites the name field in my_model and makes it blank!
Is this the expected behaviour? How would I change this so that I can ensure that if a field is not specified in the form, but it exists already in the instance that it is not overwritten with an empty string?
Note that Django only sets the name field to empty because you have set blank=True. If the field was required, there would be a validation error instead.
Recently there was a discussion on the django-developers group about changing this behaviour. The core developers Adrian and Paul were in favour of the current behaviour.
So, if you're going to use model forms for this view with models where you use blank=True, you'll need to include all the model fields in your data dict. You can use the function model_to_dict for this.
from django.forms.models import model_to_dict
data = model_to_dict(my_model)
data.update({'bio': 'this is me'}) # or data.update(request.POST)
form = MyModelForm(instance=my_model, data=data)
Providing the instance argument to a ModelForm is the same as passing initial, i.e. they serve the same purpose. What gets save is always the data. If a field in that dict is empty, then it will be when the model is saved as well.
If you want to maintain the entire state of the model when only dealing with a single field as data. Then, try something like the following:
data = my_model.__dict__
data['bio'] = request.POST.get('bio')
MyModelForm(instance=my_model, data=data)
If you want to pass initial data to the form, use initial instead of data
MyModelForm(instance=my_model, initial={'bio': 'this is me'})
^
[edit]
If you have included the field for name in your form
fields = ('name', 'bio')
but do not pass any data for "name"
data={'bio': 'this is me'}
The form field will behave as if the name had been submitted blank.
Which is allowed in your model, so is_valid() will be True
name = models.TextField(blank=True, default='')