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

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

Related

Can I override default CharField to ChoiceField in a ModelForm?

I have a (horrible) database table that will be imported from a huge spreadsheet. The data in the fields is for human consumption and is full of "special cases" so its all stored as text. Going forwards, I'd like to impose a bit of discipline on what users are allowed to put into some of the fields. It's easy enough with custom form validators in most cases.
However, there are a couple of fields for which the human interface ought to be a ChoiceField. Can I override the default form field type (CharField)? (To clarify, the model field is not and cannot be constrained by choices, because the historical data must be stored. I only want to constrain future additions to the table through the create view).
class HorribleTable( models.Model):
...
foo = models.CharField( max_length=16, blank=True, ... )
...
class AddHorribleTableEntryForm( models.Model)
class Meta:
model = HorribleTable
fields = '__all__' # or a list if it helps
FOO_CHOICES = (('square', 'Square'), ('rect', 'Rectangular'), ('circle', 'Circular') )
...?
Perhaps you could render the forms manually, passing the options through the context and make the fields in html.
Take a look at here:https://docs.djangoproject.com/en/4.0/topics/forms/#rendering-fields-manually
I think you can easily set your custom form field as long it will match the data type with the one set in your model (e.g. do not set choices longer than max_length of CharField etc.). Do the following where foo is the same name of the field in your model:
class AddHorribleTableEntryForm(forms.ModelForm):
foo = forms.ChoiceField(choices=FOO_CHOICES)
class Meta:
model = HorribleTable
...
I think this is perfectly fine for a creation form. It's will not work for updates as the values in the DB will most probably not match your choices. For that, I suggest adding a second form handling data updates (maybe with custom permission to restrict it).
UPDATE
Another approach will be to override the forms init method. That way you can handle both actions (create and update) within the same form. Let the user select from a choice field when creating an object. And display as a normal model field for existing objects:
class AddHorribleTableEntryForm(forms.ModelForm):
foo = forms.ChoiceField(choices=FOO_CHOICES)
class Meta:
model = HorribleTable
fields = '__all__' # or a list if it helps
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
instance = kwargs.get("instance", None)
if instance is None:
self.fields["foo"].widget = forms.widgets.Select(choices=self.FOO_CHOICES)

Is there a way to optimize the creation and retrieval of users (that are also part of another model) in Django with just one request?

My app has Users that can be Doctors/Patients/Secretaries. To create a Doctor, therefore, I perform two POST requests: one for the creation of a User and one for a Doctor. The way I do this, the User has to be created first so that I can later create the Doctor (Doctor requires a 'User' field). I am using Django Rest Framework to create the API.
class User(AbstractUser):
# defined roles so when I retrieve user, I know to perform a
# request to api/doctors/ or api/secretaries/ etc depending on role.
ROLES = (
('d', 'Doctor'),
('s', 'Secretary'),
('p', 'Patient'),
)
role = models.CharField(
max_length=1, choices=ROLES, blank=True, default='p', help_text='Role')
class Doctor(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
national_id = models.CharField(max_length=32, blank=False)
...
Since I'm new to Django, I don't know if two requests is the standard/best way of creating this User/Doctor.
This comes to mind as I am also thinking of the GET methods which will be performed later on (two GET requests when a Doctor logs in if I want to retrieve all of their info (User/Doctor)?)
I read about subclassing, which would be something like Doctor(User), then the only necessary request would be a single POST to create a Doctor (which would alongside create the User). I am, however, skeptical of subclassing the User as I read at least 3 SO answers stating it could cause problems in the future.
have a look at this good tutorial https://simpleisbetterthancomplex.com/tutorial/2018/01/18/how-to-implement-multiple-user-types-with-django.html which explain 2 different approches
extend AbstractUser with flags is_doctor, is_secretary, is_patient
class User(AbstractUser):
is_doctor = models.BooleanField('Doctor status', default=False)
is_secretary = models.BooleanField('Secretary status', default=False)
is_patient = models.BooleanField('Patient status', default=False)
using roles which suites your case:
class Role(models.Model):
'''
The Role entries are managed by the system,
automatically created via a Django data migration.
'''
ROLE_CHOICES = (
('d', 'Doctor'),
('s', 'Secretary'),
('p', 'Patient'),
)
id = models.PositiveSmallIntegerField(choices=ROLE_CHOICES, primary_key=True)
def __str__(self):
return self.get_id_display()
class User(AbstractUser):
roles = models.ManyToManyField(Role)
Problem
Determine best practices for handling multiple user types and adding attributes to a user in Django.
Solution
The following is are recommendations based on design patterns that have been commonly used in Django since version 0.96. These recommendations are present in Django’s documentation (see: References).
Roles
Use Django’s built in permissions module for role and group management instead of rolling your own role and group management.
Instead of a model for each type, create a single UserProfile model, relegating user types to being managed by permissions model.
I recommend using a data migration to add groups so that default groups are automatically seeded on initial migrate call—this reduces overhead for anyone setting up your project for the first time.
OneToOne
Use Django OneToOne field instead of ForeignKey.
UserProfile and Signals
Create a signal that creates a UserProfile on User create.
Example
class UserProfile(models.Model):
user = models.OneToOne(“User”, on_delete=models.CASCADE)
national_id = models.CharField(max_length=32,
def create_profile(sender, **kwargs):
user = kwargs["instance"]
if kwargs["created"]:
user_profile = UserProfile(user=user)
user_profile.save()
post_save.connect(create_profile, sender=User)
References
Django permissions (groups): https://docs.djangoproject.com/en/3.1/topics/auth/default/#groups
Django Data Migrations: https://docs.djangoproject.com/en/3.1/topics/migrations/#data-migrations
Django extending User model recommendation: https://docs.djangoproject.com/en/1.8/topics/auth/customizing/#extending-django-s-default-user
Django post_save signal: https://docs.djangoproject.com/en/3.1/ref/signals/#post-save
in your case (the model you made) you can create the doctor and the user in one post request to the doctor creation by overriding the create function for the
from rest_framework.generics import CreateAPIView, ListAPIView
class CreateDoctorViewSet(CreateAPIView, ListAPIView):
def create(self, request, *args, **kwargs):
data = self.request.data
user_dict_keys = ["username", "email", "first_name", "last_name"]
user_dict = {key: data.pop(key, None) for key in user_dict_keys}
user_dict['role'] = "d"
user_serializer = UserSerializer(data=user_dict)
# if it's not valid it will return the exception details for the requester
user_serializer.is_valid(raise_exception=True)
user = user_serializer.create(user_dict)
user.set_password(data['password'])
data.pop("password", None)
user.save()
response = super().create(request, *args, **kwargs)
if response.statu_code == 201:
return response
# if an error happened while in the doctor model (model error or serializer error) >> delete the created user
user.delete()
return response
or made a little more DRY :
from rest_framework.generics import CreateAPIView, ListAPIView
def create_user(self):
data = self.request.data
user_dict_keys = ["username", "email", "first_name", "last_name"]
user_dict = {key: data.pop(key, None) for key in user_dict_keys}
user_dict['role'] = "d"
user_serializer = UserSerializer(data=user_dict)
# if it's not valid it will return the exception details for the requester
user_serializer.is_valid(raise_exception=True)
user = user_serializer.create(user_dict)
user.set_password(data['password'])
data.pop("password", None)
user.save()
return user
class CreateDoctorViewSet(CreateAPIView, ListAPIView):
def create(self, request, *args, **kwargs):
user = create_user(request)
response = super().create(request, *args, **kwargs)
if response.statu_code == 201:
return response
# if an error happened while in the doctor model (model error or serializer error) >> delete the created user
user.delete()
return response
Personal Advices:
in the case you're providing the User model has one role so it's better to make the user field in the Doctor class OneToOne instead of ForeignKey.
Of course if you have cases where there are people for example converting from Doctor to Secretary and you want them to switch between roles on the same account you can keep the ForeignKey on Doctor model but you have to make multiple roles possible in the user model.
What you mean by subclassing it is called Multi table inheritance. And there is no problem in using it, no side effects and it is perfectly compatible with Django Rest Framework (which you have tagged). This is the way it works:
class User(AbstractUser):
# Your common fields for all user types.
class Doctor(User):
national_id = models.CharField(max_length=32, blank=False)
class Secretary(User):
# Your specific fields for secretary model
class Patient(User):
# Your specifict fields for patient model
In background, it uses a OneToOne relationship for each subtype.
Advantajes of using Multi table inheritance:
It is simple and elegant: you don't have to take care of different tables, queries, etc; Django does it for you.
It also unsures a good and formalized structure of your database: different tables for common and specific data in OneToOne relationship.
It is suitable for your needs? That depends.
If each subtype has its own specific fields -> use multi table inheritance without doubt.
If each subtype has the same set of fields but different behaviours (different class/model methods, code, etc) -> use proxy models.
If all the subtypes have the same set of fields and the same behaviour (same class/model methods, code, etc) -> use role based approach (one field identifying the role).
(extra) If you have dozens of subtypes, each one of them has different fields and you don't care too much about database formalization -> Don't use Multi table inheritance. In this case, you can use a mix of role based approach with JSON fields (for storing all the specifict fields) and proxy models (for handling different behaviours)**.

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

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 :)