What's the cleanest way to add arbitrary data to ModelForm? - python

Imagine we're developing a message system, and each Message has a foreign key for sender.
We're using ModelForms, and there is a MessageForm that infers its fields from Message.
Of course, we don't want the user to be able to spoof sender by posting a different sender ID.
Therefore, we must exclude sender from ModelForm and fill it from session on post.
Where and how should I assign arbitrary data to ModelForm fields?
In my example, I probably want to access session so we need to access to request as well.
Does this mean the code has to be in the view, right after the form has been created?
How do we assign a form field from code and make sure it overrides POST data?
(Of course, the example is pretty fictional and is here just to illustrate the problem.)

You can just exclude it as you have and then when processing the form in the view do:
obj = form.save(commit=False)
obj.sender = request.user
obj.save()
Alternatively, you can do it in the save method of the form; for this you need to save the request object momentarily in the form. Something like the following:
class MyForm(forms.ModelForm):
def __init__(self, request, *args, **kwargs):
self._request = request
super(MyForm, self).__init__(*args, **kwargs)
def save(self, commit=False):
obj = super(MyForm, self).save(commit=False)
obj.sender = self._request.user
if commit:
obj.save()
return obj
I prefer the second way myself as it helps encapsulate the logic regarding that model and it's data in one neat place.

Just exclude the sender field from the ModelForm and, when you instantiate the object in the view on the POST endpoint (just before saving), make sure you populate the sender field with the appropriate session or user ID.
When you exclude the field, there is no way to add the field to the post data[1], so a user can't spoof it.
[1] With JavaScript, a &sender=someSenderId could be added in theory, but in your view, you don't need to look for a sender field. It won't be serialized into the ModelForm object.

Related

Django: return messages in form.clean()

Suppose I have a CreateView that uses a ModelForm to add a new calendar event. Sometimes, when adding a new calendar event, another calendar event has to be added before the new event to be able to successfully validate the new event (which happens in the ModelForm.clean() method). I think the only place this other event can be added is in the clean method, just before the validation which validates the new event. Am I right that this is the only place I can do this? I want to let the user know that this happened using Django messages, but of course I don't have access to the request object in the ModelForm. That makes me think I have to add this event in another place. Which method of CreateView is suited for this purpose?
I'm sorry in advance for my English and if my question isn't worded good enough. Thanks in advance.
If you don't use ModelForm, but a custom form, then you have more control over actions. Then you can pass the request to the form and use it during your clean process.
Here is an example of one of my forms where I needed access to request to save form content:
class RatifyQuestionForm(forms.Form):
...
...
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request')
....
....
def save(self):
data = self.cleaned_data
# create approval record
ratification = Ratification.objects.create(
assessment=data['assessment'],
alternative=data['alternative'],
author=request.user)
In a similar way you could access self.request in the clean method.
You instantiate the form in your view as so:
if request.method == 'POST':
form = RatifyQuestionForm(request.POST, request=request)
If you really want to have access to request object within your form, you can override the get_form_kwargs method within the CreateView (This will give access to self.request within in your form's clean method):
class YourViewName(CreateView):
def get_form_kwargs(self):
kwargs = super(YourViewName, self).get_form_kwargs()
kwargs.update({'request': self.request})
return kwargs
But, for your scenario it is better to do the validations within the form_valid method in your view. If the validation failed, you can set the message and then call form_invalid() to re-render the form with your error message.
class YourViewName(CreateView):
def form_valid(self, form):
if not your_check_goes_here():
messages.error(self.request, _("Form is Invalid"))
return self.form_invalid(form)
return super(YourViewName, self).form_valid(form)

Django - How to make an optional field required based on request

I want to be able to save a form as a draft when it's not completely filled up and also save it as usual with classic django form validation.
To do so, I have two submit buttons in my form and I find out in my post request which button has been clicked :
class MyView(UpdateView):
def post(self, request, *args, **kwargs):
def submit_draft(self, request):
if 'draft' in request.POST:
out = True
else:
out = False
return out
In my models, all my field allow blank fields so saving an incomplete form as draft causes no issue if fields are empty.
I would like to make the form fields required when the form is saved with the normal save action.
One option I have thought of so far but didn't successfully implement :
=> Override get_form function so that when I hit Save as draft it just does it's normal action and when I hit Save, it modifies my fields required attribute.
tldr: I'm looking for a way to do something like that to my form based on the submit button that's been clicked
for field in form:
self.fields[field].required = True
Solved my problem by subclassing my forms as follows :
My form based on my model with null and blank = True
class RegionForm(forms.ModelForm):
class Meta:
model = Region
fields = ['region_name',
'region_description'
]
def __init__(self, *args, **kwargs):
super(RegionForm, self).__init__(*args, **kwargs)
for field in self.fields:
self.fields[field].required = True
class RegionDraftForm(RegionForm):
class Meta(RegionForm.Meta):
pass
def __init__(self, *args, **kwargs):
super(RegionDraftForm, self).__init__(*args, **kwargs)
for field in self.fields:
self.fields[field].required = False
That way I can instantiate in my views the form I need to save as draft or to normally save with complete form validation.
A solution I have used in the past is to serialise the content of the form into a "drafts" table when the "save draft" button is clicked.
When a user comes back to editing the form you load the json back into the form for continued editing. This can be done either using javascript or within the django form view.
Some steps you might follow:
When "Save Draft" is clicked, post the form to the draft view.
Serialise the content into json; storing the content in a "drafts model".
When a user click to re-open a draft pull the content from the database, parsing the json back into the form.
When the user clicks "Save" you save the form contents into the database and delete the draft from the "drafts table".
This is an example of what your Drafts model could look like.
class Drafts(models.Model):
user = models.ForeignKey(User)
form_name = models.CharField(max_length=100)
serialised_form = models.TextField() # could use json field here
def to_dict(self):
return json.loads(self.serialised_form)
I see this method having a number of advantages over

How can modify response send by django rest framework

I am using Django REST as backend and angular as front end.
I have two serializers one for readong GET requests and other for POST, PUT requests.
This was because there are few fields like interval, etc which i am entering in integer by user but in the database i am saving as timedelta so i have to multitply them to make them as seconds on front end.
so e.g interval = 5 entered by user and i am posting 5*60*60 to server.
In order to read i have made ReadSerializer where i am diving that by 60*60 to again show to user what he added.
This is working find my problem is after saving my object to database the djnago rest frameework sends the object as it is saved which has interval = 5*60*60. inerval is just an example there 4-5 felds where i am changing them in front end before posting
Is there any way that response used my READ serializer before sending
class Serializer(serializers.ModelSerializer):
interval = serializers.SerializerMethodField('get_interval')
class Meta:
model = User
fields = ('id', 'interval')
def get_interval(self, obj):
return obj.interval/60*60
class WriteSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'interval')
This is the view
class UserListCreateView(generics.ListCreateAPIView):
queryset = User.objects.filter(is_deleted=False)
def get_serializer_class(self):
if self.request.method == 'GET':
return ReadSerializer
return WriteSerializer
You can use the transform_field method which allows you to customize the representation of a field when showing it.
Documentation about it: http://www.django-rest-framework.org/api-guide/serializers/#customizing-field-representation
Btw, careful if you are planning to upgrade to new versions of drf (>=3), these methods may disappear in favour of a unique to_representation method: https://groups.google.com/forum/?fromgroups#!topic/django-rest-framework/9kp5kXCssR4
I would just change it in JavaScript on the front end.
However I have a similar issue and solved it like this, (warning, I just started using DRF, so may not be the best way):
Just change the response of the method directly by first getting the original response, then editing the data property of it, which is a dict (pre-serialized) of the data to return:
class UserListCreateView(generics.ListCreateAPIView):
...
def list(self, request, *args, **kwargs):
response = super().list(request, *args, **kwargs)
response.data['interval'] = response.data['interval'] / 60 / 60
return response

Inside Django's models.py, if I am validating a form, how do I get the user's IP?

I know how to get it in views.py....
request.META['REMOTE_ADDR']
However, how do I get it in models.py when one of my forms is being validateD?
You can pass the request object to the form/model code that is being called: this will then provide access to request.META['REMOTE_ADDR']. Alternatively, just pass that in.
Ona possible way, but i am not sure if it is the best or not...
define your own clean method,
class someForm(forms.Form):
afield = CharField()
def clean(self, **kwargs):
cleaned_data = self.cleaned_data
afield = cleaned_data.get('afield')
if 'ip' in kwargs:
ip = kwargs['ip']
# ip check block, you migth use your cleaned data in here
return cleaned_data
some_info = {'afield':123} #you will wish to use post or gt form data instead, but tihs iis for example
form = someForm(some_info)
if form.is_valid():
data = form.clean({'ip':request.META['REMOTE_ADDR']}) # you pass a dict with kwargs, which wwill be used in custom clean method
If you are validating at form level or at model level, both instances know nothing about the HTTP request (where the client IP info is stored).
I can think of two options:
Validate at the view level where you can insert errors into the form error list.
You can put the user IP (may be encrypted) in a hidden field at your form.

Django: How to set initial values for a field in an inline model formset?

I have what I think should be a simple problem. I have an inline model formset, and I'd like to make a select field have a default selected value of the currently logged in user. In the view, I'm using Django's Authentication middleware, so getting the user is a simple matter of accessing request.user.
What I haven't been able to figure out, though, is how to set that user as the default selected value in a select box (ModelChoiceField) containing a list of users. Can anyone help me with this?
This does the trick. It works by setting the initial values of all "extra" forms.
formset = MyFormset(instance=myinstance)
user = request.user
for form in formset.forms:
if 'user' not in form.initial:
form.initial['user'] = user.pk
I'm not sure how to handle this in inline formsets, but the following approach will work for normal Forms and ModelForms:
You can't set this as part of the model definition, but you can set it during the form initialization:
def __init__(self, logged_in_user, *args, **kwargs):
super(self.__class__, self).__init__(*args, **kwargs)
self.fields['my_user_field'].initial = logged_in_user
...
form = MyForm(request.user)
I'm using Rune Kaagaard's idea above, except I noticed that formsets provide an extra_forms property: django.forms.formsets code
#property
def extra_forms(self):
"""Return a list of all the extra forms in this formset."""
return self.forms[self.initial_form_count():]
So, sticking with the example above:
formset = MyFormset(instance=myinstance)
user = request.user
for form in formset.extra_forms:
form.initial['user'] = user.pk
Saves having to test any initial forms, just provide default for extra forms.

Categories