Django single model multiple form - django

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."
)

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

Forms fields respecting DRY 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='')

Filter M2M in template?

In my model, I have the following M2M field
class FamilyMember(AbstractUser):
...
email_list = models.ManyToManyField('EmailList', verbose_name="Email Lists", blank=True, null=True)
...
The EmailList table looks like this:
class EmailList(models.Model):
name = models.CharField(max_length=50, default='My List')
description = models.TextField(blank=True)
is_active = models.BooleanField(verbose_name="Active")
is_managed_by_user = models.BooleanField(verbose_name="User Managed")
In the app, the user should only see records that is_active=True and is_managed_by_user=True.
In the Admin side, the admin should be able to add a user to any/all of these groups, regardless of the is_active and is_managed_by_user flag.
What happens is that the Admin assigns a user to all of the email list records. Then, the user logs in and can only see a subset of the list (is_active=True and is_managed_by_user=True). This is expected behavior. However, what comes next is not.
The user deselects an email list item and then saves the record. Since M2M_Save first clears all of the m2m records before it calls save() I lose all of the records that the Admin assigned to this user.
How can I keep those? I've tried creating multiple lists and then merging them before the save, I've tried passing the entire list to the template and then hiding the ones where is_managed_by_user=False, and I just can't get anything to work.
What makes this even more tricky for me is that this is all wrapped up in a formset.
How would you go about coding this? What is the right way to do it? Do I filter out the records that the user shouldn't see in my view? If so, how do I merge those missing records before I save any changes that the user makes?
You might want to try setting up a model manager in your models.py to take care of the filtering. You can then call the filter in your views.py like so:
models.py:
class EmailListQuerySet(models.query.QuerySet):
def active(self):
return self.filter(is_active=True)
def managed_by_user(self):
return self.filter(is_managed_by_user=True)
class EmailListManager(models.Manager):
def get_queryset(self):
return EmailListQuerySet(self.model, using=self._db)
def get_active(self):
return self.get_queryset().active()
def get_all(self):
return self.get_queryset().active().managed_by_user()
class EmailList(models.Model):
name = models.CharField(max_length=50, default='My List')
description = models.TextField(blank=True)
is_active = models.BooleanField(verbose_name="Active")
is_managed_by_user = models.BooleanField(verbose_name="User Managed")
objects = EmailListManager()
views.py:
def view(request):
email = EmailList.objects.get_all()
return render(request, 'template.html', {'email': email})
Obviously there is outstanding data incorporated in my example, and you are more than welcome to change the variables/filters according to your needs. However, I hope the above can give you an idea of the possibilities you can try.
In your views you could do email = EmailList.objects.all().is_active().is_managed_by_user(), but the loading time will be longer if you have a lot of objects in your database. The model manager is preferred to save memory. Additionally, it is not reliant on what the user does, so both the admin and user interface have to talk to the model directly (keeping them in sync).
Note: The example above is typed directly into this answer and has not been validated in a text editor. I apologize if there are some syntax or typo errors.

Django Form with no required fields

I want to make a form used to filter searches without any field being required. For example given this code:
models.py:
class Message(models.Model):
happened = models.DateTimeField()
filename = models.CharField(max_length=512, blank=True, null=True)
message = models.TextField(blank=True, null=True)
dest = models.CharField(max_length=512, blank=True, null=True)
fromhost = models.ForeignKey(Hosts, related_name='to hosts', blank=True, null=True)
TYPE_CHOICES = ( (u'Info', u'Info'), (u'Error', u'Error'), (u'File', u'File'), (u'BPS', u'BPS'),)
type = models.CharField(max_length=7, choices=TYPE_CHOICES)
job = models.ForeignKey(Jobs)
views.py:
WHEN_CHOICES = ( (u'', ''), (1, u'Today'), (2, u'Two days'), (3, u'Three Days'), (7, u'Week'),(31, u'Month'),)
class MessageSearch(ModelForm): #Class that makes a form from a model that can be customized by placing info above the class Meta
message = forms.CharField(max_length=25, required=False)
job = forms.CharField(max_length=25, required=False)
happened = forms.CharField(max_length=14, widget=forms.Select(choices=WHEN_CHOICES), required=False)
class Meta:
model = Message
That's the code I have now. As you can see it makes a form based on a model. I redefined message in the form because I'm using an icontains filter so I didn't need a giant text box. I redefined the date mostly because I didn't want to have to mess around with dates (I hate working with dates! Who doesnt?) And I changed the jobs field because otherwise I was getting a drop down list of existing jobs and I really wanted to be able to search by common words. So I was able to mark all of those as not required
The problem is it's marking all my other fields as required because in the model they're not allowed to be blank.
Now in the model they can't be blank. If they're blank then the data is bad and I don't want it in the DB. However the form is only a filter form on a page to display the data. I'm never going to save from that form so I don't care if fields are blank or not. So is there an easy way to make all fields as required=false while still using the class Meta: model = Message format in the form? It's really handy that I can make a form directly from a model.
Also this is my first serious attempt at a django app so if something is absurdly wrong please be kind :)
You can create a custom ModelForm that suit your needs. This custom ModelForm will override the save method and set all fields to be non-required:
from django.forms import ModelForm
class SearchForm(ModelForm):
def __init__(self, *args, **kwargs):
super(SearchForm, self).__init__(*args, **kwargs)
for key, field in self.fields.iteritems():
self.fields[key].required = False
So you could declare your forms by simply calling instead of the ModelForm, e.g.:
class MessageForm(SearchForm):
class Meta:
model = Message
You could also pass empty_permitted=True when you instantiate the form, e.g.,
form = MessageSearch(empty_permitted=True)
that way you can still have normal validation rules for when someone does enter data into the form.
I would give a try to the django-filter module :
http://django-filter.readthedocs.io/en/develop/
fields are not required. these are filters actually. It would look like this :
import django_filters
class MessageSearch(django_filters.FilterSet):
class Meta:
model = Message
fields = ['happened', 'filename', 'message', '...', ]
# django-filter has its own default widgets corresponding to the field
# type of the model, but you can tweak and subclass in a django way :
happened = django_filters.DateFromToRangeFilter()
mandatory, hidden filters can be defined if you want to narrow a list of model depending on something like user rights etc.
also : setup a filter on a 'reverse' relationship (the foreignkey is not in the filtered model : the model is referenced elsewhere in another table), is easy, just name the table where the foreign key of the filtered model field is :
# the 'tags' model has a fk like message = models.ForeignKey(Message...)
tags= django_filters.<some filter>(name='tags')
quick extendable and clean to setup.
please note I didn't wrote this module, I'm just very happy with it :)

Validating an Autocomplete field in Django

I have models similar to the following:
class Band(models.Model):
name = models.CharField(unique=True)
class Event(models.Model):
name = models.CharField(max_length=50, unique=True)
bands = models.ManyToManyField(Band)
and essentially I want to use the validation capability offered by a ModelForm that already exists for Event, but I do not want to show the default Multi-Select list (for 'bands') on the page, because the potential length of the related models is extremely long.
I have the following form defined:
class AddEventForm(ModelForm):
class Meta:
model = Event
fields = ('name', )
Which does what is expected for the Model, but of course, validation could care less about the 'bands' field. I've got it working enough to add bands correctly, but there's no correct validation, and it will simply drop bad band IDs.
What should I do so that I can ensure that at least one (correct) band ID has been sent along with my form?
For how I'm sending the band-IDs with auto-complete, see this related question: Django ModelForm Validate custom Autocomplete for M2M, instead of ugly Multi-Select
You can override the default fields in a ModelForm.
class AddEventForm(forms.ModelForm):
band = forms.CharField(max_length=50)
def clean_band(self):
bands = Band.objects.filter(name=band,
self.data.get('band', ''))
if not bands:
raise forms.ValidationError('Please specify a valid band name')
self.cleaned_data['band_id'] = bands[0].id
Then you can use your autocomplete widget, or some other widget. You can also use a custom widget, just pass it into the band field definition: band = forms.CharField(widget=...)