dynamic form requirements from model - django-forms

I'm trying to generate a dynamic registration form, based on on specific client needs. I've created a UserProfile model with most of the fields set as blank=True.
When the form gets generated, I pull the client specified fields from another db table and generate the registration form. All this works, except that all the fields allow blank values. So far I have this
def RegProfileForm(include_list, *args, **kwargs):
class ProfileForm(forms.ModelForm):
class Meta:
model = hr.UserProfile
fields = include_list
def __init__(self):
super(ProfileForm, self).__init__(*args, **kwargs)
return ProfileForm()
Then I call this form like this:
includes = ['gender','work_phone'] # dynamic fields
of = RegProfileForm(includes)
How do I dynamically remove the blank=True requirement from certain specified fields during runtime, or when I generate the form?

I figured out I can just do something like this to override the default values from the model:
form = MyAuthForm(data)
form.fields['first_name'].required = True
form.fields['email'].required = False

Related

How to add a custom attribute to the hidden id field from a formset modelform

I'm trying to add a "form" attribute to the modelform used by a formset so that I can put my form elements into a table as described here Form inside a table
I found this answer which seems to be what I wanted How to add class, id, placeholder attributes to a field in django model forms
I've implemented this as shown in my forms.py
class EditAssemblyForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(EditAssemblyForm, self).__init__(*args, **kwargs)
for field in self.fields:
self.fields[field].widget.attrs.update(
{'form': 'EditAssemblyForm'})
self.fields['issue'].required = False
self.fields['qty'].widget.attrs['min'] = 1
#self.fields['id'].widget.attrs.update({'form': 'EditAssemblyForm'})
class Meta:
model = Relationship
fields = ['issue', 'qty']
This is working for the issue and qty form elements but not for the hidden id field. I tried to specifically apply this to self.fields['id'] as can be seen in the commented line but this causes an error:
Exception Type: KeyError
Exception Value: 'id'
Exception Location: C:\SvnDeltaRepository\DeltaSoftware\django\delta\PaNDa\forms.py, line 98, in __init__
I ended up adding it in the views.py instead:
for form in formset:
form.fields.get('id').widget.attrs['form'] = 'EditAssemblyForm'
Seems to work
--edit--
Also had to add:
for field in formset.management_form.fields:
formset.management_form.fields[field].widget.attrs['form'] = 'EditAssemblyForm'
This adds the same attribute to the hidden fields that the management_form generates. Without this the formset.is_valid() would fail.

Django: customizing the field types needed for create and retrieve serializers

I currently have the following serializer:
serializers.py
class SurfGroupSerializer(serializers.ModelSerializer):
instructor = SurfInstructorSerializer(many=False)
surfers = SurferSerializer(many=True)
class Meta:
model = SurfGroup
fields = ['uuid', 'instructor', 'date', 'starting_time', 'ending_time', 'surfers']
def create(self, validated_data):
return SurfGroup(**validated_data)
And the following viewset create method (viewset inherited from viewsets.ViewSet as we need some bespoke customization, extra signals and actions etc):
viewsets.py
# Surf Group Create View:
def create(self, request, format=None):
serializer = SurfGroupSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
response = responses.standardized_json_response(
message='Surf Group Objects Have Been Successfully Created',
data=serializer.data
)
return Response(data=response, status=status.HTTP_201_CREATED, headers=headers)
For the retrieve action, the serializer works well, and we have a nested instructor object in the response. However, I want to perform a create by passing in the instructor uuid attrbiute like (see content in the POST textarea):
Rather than a whole object...I was wondering how we achieve this? Is it best to just have two Serializers, one for performing the create, and one the retrieval?
def create(self, validated_data):
surf_group = SurfGroup(
instructor__uuid=validated_data['instructor'],
)
surf_group.save()
return surf_group
It is good question.
I work with this situations many times and it looks like one option is to have two serializers as you mean: 1 for list/retrieve and 1 for save.
Another option (for me) is to set serializer field input as UUID and output as another serializer data like this:
class SurfGroupSerializer(serializers.ModelSerializer):
instructor = serializers.UUIDField()
surfers = SurferSerializer(many=True, read_only=True)
class Meta:
model = SurfGroup
fields = ['uuid', 'instructor', 'date', 'starting_time', 'ending_time', 'surfers']
# I use this validate method to transform uuid to object which will
# be bypassed to create method for easly save
def validate_instructor(self, instructor_uuid):
try:
return Instructor.objects.get(uuid=instructor_uuid)
except Instructor.DoesNotExist:
# Remember that you dont need to pass field_key: [errors] to ValidationError
# because validate_FIELD will automatically pass field_key for you !
raise ValidationError(['Instructor with the given uuid does not exist.'])
# Overwrite output data
def to_representation(self, instance):
ret = super().to_representation(instance)
ret['instructor'] = SurfInstructorSerializer(instance=instance.instructor).data
return ret

Django REST serializer required field

I have a simple serializer with one required field:
class MySerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = '__all__'
read_only_fields = ('field1', 'field2')
In my model there is an 'url' field which is required to create new object (method: POST). I would like to set required: False for PUT method. How can I achieve that? Thanks for any clues...
I assume you want to change/set one or multiple fields of an existing MyModel instance.
In such case, you need to pass a partial=True keyword argument to serializer. Then even if you PUT or PATCH without url field in data, your serializer.is_valid() would evaluate to True.
https://www.agiliq.com/blog/2019/04/drf-polls/#edit-a-poll-question should help if my assumption about your question is correct.
I found this answer helpful: Django Rest Framework set a field read_only after record is created .
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance is not None:
self.fields.get('url').read_only = True
This code works fine.

Model Serializer : choose which fields to display and add custom fields

Let's say I have this simple model :
class BlogPost(models.Model):
author = models.ForeignKey(MyUser)
body = models.TextField()
title = models.CharField(max_length=64)
urlid = models.CharField(max_length=32)
private_data = models.CharField(max_length=64)
private_data contains data that I do not want to expose to the API (!). I'm using a ModelSerializer :
class BlogPostSerializer(serializers.ModelSerializer):
class Meta:
model = BlogPost
def __init__(self, *args, **kwargs):
# Don't pass the 'request' arg up to the superclass
request = kwargs.pop('request', None)
# Instatiate the superclass normally
super(ModelSerializer, self).__init__(*args, **kwargs)
self.request = request
def absolute_url(self, blogpost):
return blogpost.get_absolute_url(self.request)
The absolute_url method needs the request to determine the domain name (dev or prod for example) and if it was made in http or https.
I want to specify which fields in the model are going to get returned by the serializer (not expose private_data for example). Simple enough:
class BlogPostSerializer(serializers.ModelSerializer):
class Meta:
model = BlogPost
fields = ('author', 'body', 'title', 'urlid',)
# The same jazz after that
All right, it works. Now I also want to return absoluteUrl:
class BlogPostSerializer(serializers.ModelSerializer):
absoluteUrl = serializers.SerializerMethodField('absolute_url')
class Meta:
model = BlogPost
fields = ('author', 'body', 'title', 'urlid',)
# The same jazz after that
Well, without surprises, this returns only the fields I specified, without the absoluteUrl. How can I return only certain fields of the model AND the absoluteUrl, calculated from the serializer?
If I don't specify fields I do get the absoluteUrl, but with all the model's fields (including private_data). If I add 'absoluteUrl' to fields I get an error because blogpost.absoluteUrl doesn't exist (no surprises there). I don't think I could use this method http://django-rest-framework.org/api-guide/serializers.html#specifying-fields-explicitly because I need the request to obtain the absoluteUrl (or can I specify arguments to the model's method ?)
If I don't specify fields I do get the absoluteUrl, but with all the model's fields (including private_data). If I add 'absoluteUrl' to fields I get an error because blogpost.absoluteUrl doesn't exist (no surprises there).
You should just be adding 'absoluteUrl' to the fields tuple, and it should work just fine - so what error are you seeing?
The absolute_url method needs the request to determine the domain name (dev or prod for example) and if it was made in http or https.
Note that you can also pass through context to the serializer without modfiying the __init__, just pass a context={'request': request} when instantiating the serializer. The default set of generic views do this for you, so you can access self.context['request'] in any of the serializer methods. (Note that this is how hyperlinked relationships are able to return fully qualified URLs)

How to subclass django's generic CreateView with initial data?

I'm trying to create a dialog which uses jquery's .load() function to slurp in a rendered django form. The .load function is passed the pk of the "alert" object. Also available in the class functions are things like self.request.user so I can pre-fill those fields, shown below in the Message model (models.py):
class Message(models.Model):
user = models.ForeignKey(User)
alert = models.ForeignKey(Alert)
date = models.DateTimeField()
message = models.TextField()
Subclassing django's CreateView makes it pretty easy to generate a context with an instance of the ModelForm (views.py):
class MessageDialogView(CreateView):
""" show html form fragment """
model = Message
template_name = "message.html"
def get_initial(self):
super(MessageDialogView, self).get_initial()
alert = Alert.objects.get(pk=self.request.POST.get("alert_id"))
user = self.request.user
self.initial = {"alert":alert.id, "user":user.id, "message":"test"}
return self.initial
def post(self, request, *args, **kwargs):
super(MessageDialogView, self).post(request, *args, **kwargs)
form_class = self.get_form_class()
form = self.get_form(form_class)
context = self.get_context_data(form=form)
return self.render_to_response(context)
The problem here is that self.initial does not get rendered with the form. I have insured that the form is indeed calling get_initial and the form instance has the proper initial data in post, but when the form is rendered in the template message.html it doesn't grab any of the initial data like I would expect. Is there a special trick to get this to work? I've scoured the docs (seems to be lacking examples on generic based class views) and source but I can't see what I'm missing.
get_initial() should just return a dictionary, not be bothered with setting self.initial.
Your method should look something like this:
def get_initial(self):
# Get the initial dictionary from the superclass method
initial = super(YourView, self).get_initial()
# Copy the dictionary so we don't accidentally change a mutable dict
initial = initial.copy()
initial['user'] = self.request.user.pk
# etc...
return initial
you can use like :
from django.shortcuts import HttpResponseRedirect
class PostCreateView(CreateView):
model = Post
fields = ('title', 'slug', 'content', 'category', 'image')
template_name = "create.html"
success_url = '/'
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.user = self.request.user
self.object.save()
return HttpResponseRedirect(self.get_success_url())
that's work for me
(Edited because what you're trying does actually work)
I ran into the same problem yesterday, but it's working now – I think I was returning an object instead of a dict in get_initial.
In terms of fixing your problem, I'm a little suspicious of how much you seem to be doing in post() – could you try it with the default (non-overrided) post()?
You could also use pdb (or print statements) to check the value of self.get_form_kwargs make sure that initial is being set.

Resources