Django pass initial values from object in Model Form - python

I have django model form MyModelFormA for the model ModelA (I am using these in FormView).
I want to pass initial values to the form using existing object of ModelA and create new object of it if changes occur.
I have been passing initial values like below:
def get_form_kwargs(self):
kwargs = super(MyFormView, self).get_form_kwargs()
kwargs.update({'instance': ModelAObject})
I'm not sure why but when the form is validated like below
def form_valid(self, form):
instance = form.save()
Its just updating existing instance object instead of creating new.
HTTP requests being stateless, how does the request knows an instance that is being passed processed in previous request
How to solve this?
I thought of doing something like
def get_initial(self):
initial = model_to_dict(MyModelAObject)
return initial
Actually There are only a subset of MyModelA fields in MyModelFormA. Passing all fields as dict initially, wouldn't create any trouble?
is there any much elegant way to handle it?

When you pass ModelForm an instance, it sets id field of that instance as initial of the form as well. So, if it receives an ID in the POST, its treats it as a existing object and updates it
You will need to pass individual field's initial value(i.e except id). The best way is to only pass the fields you need, in ModelForm, as initial.
def get_initial(self):
return {
'a': MyModelAObject.a,
....
}

Probably you can try this:
def form_valid(self, form):
if form.has_changed()
instance = form.save(commit=False)
instance.pk = None
#if you have id
instance.id = None
instance.save() #will give you new instance.
Check In Django 1.4, do Form.has_changed() and Form.changed_data, which are undocumented, work as expected? to see how form.has_changed() will work.

Related

How to update another model field from updateview

How can I use the current value of a field in update view to update another field?
I have a model employee. It has a field day_absent and amount_absent which shows the total deduction amount based on how many day_absent.
In update view the can set the day_absent.
When I can back to DetailView, say the day_absent is 1. but the deduction is still 0. I understand that this is because prior to save(), day_absent was still 0 and was changed to 1. So the question is how can i compute whatever is value entered in day_absent before it gets saved?
class PayrollTransactionUpdate(LoginRequiredMixin,UpdateView):
model = t_pay
template_name = 'payroll/transaction/update.html'
fields = ['day_absent']
def post(self,request,pk):
emp = t_pay.objects.get(pk=pk)
emp.amt_absent = emp.day_absent * emp.amt_rate
emp.save()
return super().post(request)
The UpdateView saves the object in the form_valid() method. Look at this invaluable site when you're using Django class-based views.
So you should override the form_valid() method, not the post() method:
def form_valid(self, form):
emp = form.save(commit=False)
emp.amt_absent = emp.day_absent * emp.amt_rate
emp.save()
self.object = amp
return super().form_valid(form)
Note that saving a field to Employee that can easily be calculated from two other fields is not recommended, as it could lead to inconsistent/corrupt data. Since amt_absent is just the multiplication of two other fields, why do you need to save it?
Alternatively, looking at what you're actually doing, this does not seem the responsibility of the view. If this is supposed to happen every time an Employee is saved, you could do it on the model itself:
class Employee(Model):
... # fields go here
def save(self, **kwargs):
self.amt_absent = self._get_amt_absent()
super().save(**kwargs)
# update other models here as well
In this situation, you don't need to override anything on the UpdateView.

Append additional values to queryset in Django generic views before handing to template

I have the following view:
class AppointmentListView(LoginRequiredMixin, ListView):
queryset = Appointment.objects.prefetch_related('client','patients')
I need to be able to add an extra variable to each returned Appointment object based on the following:
status_choices={
'STATUS_UPCOMING':'default',
'STATUS_ARRIVED':'primary',
'STATUS_IN_CONSULT': 'success',
'STATUS_WAITING_TO_PAY':'info',
'STATUS_PAYMENT_COMPLETE':'warning',
}
The values ('default', 'primary' etc) correspond to standard css classesin Bootcamp themes that I want to use according to the type of Appointment. For example, 'default' produces a gray button, 'warning' a red button etc.
I need to map each Appointment record to a certain css button based on the record's status ('upcoming' would display the 'default' class etc).
My initial idea was to loop over the query set and build a separate array/dictionary mapping the Appointment pk to a given css class such as
1:'success', 2:'warning', and then pass that in as a context variable.
But I was wondering if I could just add the value to each Appointment object directly (perhaps saving the queryset as a list?) That would be a much cleaner solution but am not sure how that should be approached.
Any ideas much appreciated
You should overload the get_queryset method of the ListView like so
def get_queryset(self, **kwargs):
queryset = super(AppointmentListView, self).get_queryset(**kwargs)
# Add new elements here
...
return queryset
I got this working by overriding get_queryset() and giving the objects (i.e. each row in the db) an extra on-the-fly key/value:
class AppointmentListView(LoginRequiredMixin,ListView):
#friendly template context
context_object_name = 'appointments'
template_name = 'appointments/appointment_list.html'
def get_queryset(self):
qs = Appointment.objects.prefetch_related('client','patients')
for r in qs:
if r.status == r.STATUS_UPCOMING: r.css_button_class = 'default'
if r.status == r.STATUS_ARRIVED: r.css_button_class = 'warning'
if r.status == r.STATUS_IN_CONSULT: r.css_button_class = 'success'
if r.status == r.STATUS_WAITING_TO_PAY: r.css_button_class = 'danger'
if r.status == r.STATUS_PAYMENT_COMPLETE: r.css_button_class = 'info'
return list(qs)
A couple of things:
I converted the queryset qs to a list to 'freeze' it. This prevents the queryset from being re-evaluated (e.g. slice) which, in turn, would cause the on-the-fly model changes to be lost as fresh data is pulled from DB.
I needed to assign a value to template_name explicitly. When overriding get_queryset the template name is not derived automagically. As a comparison, the code below whose queryset attribute is set, generates the template name automatically:
class AppointmentListView(LoginRequiredMixin, ListView):
queryset = Appointment.objects.prefetch_related('client', 'patients')
#template name FOO_list derived automatically
#appointments/views.py
...
#can use derived name (FOO_list)
{% for appointment in appointment_list %}
...

Django: How to unit test Update Views/Forms

I'm trying to unit test my update forms and views. I'm using Django Crispy Forms for both my Create and Update Forms. UpdateForm inherits CreateForm and makes a small change to the submit button text. The CreateView and UpdateView are very similar. They have the same model, template, and success_url. They differ in that they use their respective forms, and CreateView inherits django.views.generic.CreateView, and UpdateView inherits django.views.generic.edit.UpdateView.
The website works fine. I can create and edit an object without a problem. However, my second test shown below fails. How do I test my UpdateForm?
Any help would be appreciated. Thanks.
This test passes:
class CreateFormTest(TestCase):
def setUp(self):
self.valid_data = {
'x': 'foo',
'y': 'bar',
}
def test_create_form_valid(self):
""" Test CreateForm with valid data """
form = CreateForm(data=self.valid_data)
self.assertTrue(form.is_valid())
obj = form.save()
self.assertEqual(obj.x, self.valid_data['x'])
This test fails:
class UpdateFormTest(TestCase):
def setUp(self):
self.obj = Factories.create_obj() # Creates the object
def test_update_form_valid(self):
""" Test UpdateForm with valid data """
valid_data = model_to_dict(self.obj)
valid_data['x'] = 'new'
form = UpdateForm(valid_data)
self.assertTrue(form.is_valid())
case = form.save()
self.assertEqual(case.defendant, self.valid_data['defendant']
When pre-populating a ModelForm with an object that has already been created you can use the instance keyword argument to pass the object to the form.
form = SomeForm(instance=my_obj)
This can be done in a test, such as in the OP< or in a view to edit an object that has already been created. When calling save() the existing object will updated instead of creating a new one.

Using #list_route and #detail_route together

I have a special operation on a model A, which adds objects (instances of model B) to a m2m relationship. I want to be able to both add instances of model B in the same request that I am creating an instance of model A. To do this I would use #list_route(methods=['post']).
But I also want to be able to add instances of model B to an instance of model A which has already been created. For this I would use #detail_route(methods=['patch']). However, the only difference in how these to views operate is in fetching/creating the instance of model A. Ideally I would make this a single function that tests request.method when fetching an instance of model A. That would look something like this:
#list_route(methods=['post'])
#detail_route(methods=['patch'])
def my_view(self, request, *args, **kwargs):
if request.method == 'POST':
# create and fetch instance of model A
instance = A.objects.create(some_paramaters_here)
else if request.method == 'PATCH':
instance = self.get_object()
# do things to add to instance
for thing in request:
instance.add(thing)
return 201 # and fetch json output from serializer that I omitted here
But I'm really concerned about using both decorators here. Will it work? Are best practices in this scenario any different?

Dynamically limiting queryset of related field

Using Django REST Framework, I want to limit which values can be used in a related field in a creation.
For example consider this example (based on the filtering example on https://web.archive.org/web/20140515203013/http://www.django-rest-framework.org/api-guide/filtering.html, but changed to ListCreateAPIView):
class PurchaseList(generics.ListCreateAPIView)
model = Purchase
serializer_class = PurchaseSerializer
def get_queryset(self):
user = self.request.user
return Purchase.objects.filter(purchaser=user)
In this example, how do I ensure that on creation the purchaser may only be equal to self.request.user, and that this is the only value populated in the dropdown in the form in the browsable API renderer?
I ended up doing something similar to what Khamaileon suggested here. Basically I modified my serializer to peek into the request, which kind of smells wrong, but it gets the job done... Here's how it looks (examplified with the purchase-example):
class PurchaseSerializer(serializers.HyperlinkedModelSerializer):
def get_fields(self, *args, **kwargs):
fields = super(PurchaseSerializer, self).get_fields(*args, **kwargs)
fields['purchaser'].queryset = permitted_objects(self.context['view'].request.user, fields['purchaser'].queryset)
return fields
class Meta:
model = Purchase
permitted_objects is a function which takes a user and a query, and returns a filtered query which only contains objects that the user has permission to link to. This seems to work both for validation and for the browsable API dropdown fields.
Here's how I do it:
class PurchaseList(viewsets.ModelViewSet):
...
def get_serializer(self, *args, **kwargs):
serializer_class = self.get_serializer_class()
context = self.get_serializer_context()
return serializer_class(*args, request_user=self.request.user, context=context, **kwargs)
class PurchaseSerializer(serializers.ModelSerializer):
...
def __init__(self, *args, request_user=None, **kwargs):
super(PurchaseSerializer, self).__init__(*args, **kwargs)
self.fields['user'].queryset = User._default_manager.filter(pk=request_user.pk)
The example link does not seem to be available anymore, but by reading other comments, I assume that you are trying to filter the user relationship to purchases.
If i am correct, then i can say that there is now an official way to do this. Tested with django rest framework 3.10.1.
class UserPKField(serializers.PrimaryKeyRelatedField):
def get_queryset(self):
user = self.context['request'].user
queryset = User.objects.filter(...)
return queryset
class PurchaseSeriaizer(serializers.ModelSerializer):
users = UserPKField(many=True)
class Meta:
model = Purchase
fields = ('id', 'users')
This works as well with the browsable API.
Sources:
https://github.com/encode/django-rest-framework/issues/1985#issuecomment-328366412
https://medium.com/django-rest-framework/limit-related-data-choices-with-django-rest-framework-c54e96f5815e
I disliked the style of having to override the init method for every place where I need to have access to user data or the instance at runtime to limit the queryset. So I opted for this solution.
Here is the code inline.
from rest_framework import serializers
class LimitQuerySetSerializerFieldMixin:
"""
Serializer mixin with a special `get_queryset()` method that lets you pass
a callable for the queryset kwarg. This enables you to limit the queryset
based on data or context available on the serializer at runtime.
"""
def get_queryset(self):
"""
Return the queryset for a related field. If the queryset is a callable,
it will be called with one argument which is the field instance, and
should return a queryset or model manager.
"""
# noinspection PyUnresolvedReferences
queryset = self.queryset
if hasattr(queryset, '__call__'):
queryset = queryset(self)
if isinstance(queryset, (QuerySet, Manager)):
# Ensure queryset is re-evaluated whenever used.
# Note that actually a `Manager` class may also be used as the
# queryset argument. This occurs on ModelSerializer fields,
# as it allows us to generate a more expressive 'repr' output
# for the field.
# Eg: 'MyRelationship(queryset=ExampleModel.objects.all())'
queryset = queryset.all()
return queryset
class DynamicQuersetPrimaryKeyRelatedField(LimitQuerySetSerializerFieldMixin, serializers.PrimaryKeyRelatedField):
"""Evaluates callable queryset at runtime."""
pass
class MyModelSerializer(serializers.ModelSerializer):
"""
MyModel serializer with a primary key related field to 'MyRelatedModel'.
"""
def get_my_limited_queryset(self):
root = self.root
if root.instance is None:
return MyRelatedModel.objects.none()
return root.instance.related_set.all()
my_related_model = DynamicQuersetPrimaryKeyRelatedField(queryset=get_my_limited_queryset)
class Meta:
model = MyModel
The only drawback with this is that you would need to explicitly set the related serializer field instead of using the automatic field discovery provided by ModelSerializer. i would however expect something like this to be in rest_framework by default.
In django rest framework 3.0 the get_fields method was removed. But in a similar way you can do this in the init function of the serializer:
class PurchaseSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Purchase
def __init__(self, *args, **kwargs):
super(PurchaseSerializer, self).__init__(*args, **kwargs)
if 'request' in self.context:
self.fields['purchaser'].queryset = permitted_objects(self.context['view'].request.user, fields['purchaser'].queryset)
I added the if check since if you use PurchaseSerializer as field in another serializer on get methods, the request will not be passed to the context.
First to make sure you only allow "self.request.user" when you have an incoming http POST/PUT (this assumes the property on your serializer and model is named "user" literally)
def validate_user(self, attrs, source):
posted_user = attrs.get(source, None)
if posted_user:
raise serializers.ValidationError("invalid post data")
else:
user = self.context['request']._request.user
if not user:
raise serializers.ValidationError("invalid post data")
attrs[source] = user
return attrs
By adding the above to your model serializer you ensure that ONLY the request.user is inserted into your database.
2) -about your filter above (filter purchaser=user) I would actually recommend using a custom global filter (to ensure this is filtered globally). I do something for a software as a service app of my own and it helps to ensure each http request is filtered down (including an http 404 when someone tries to lookup a "object" they don't have access to see in the first place)
I recently patched this in the master branch so both list and singular views will filter this
https://github.com/tomchristie/django-rest-framework/commit/1a8f07def8094a1e34a656d83fc7bdba0efff184
3) - about the api renderer - are you having your customers use this directly? if not I would say avoid it. If you need this it might be possible to add a custom serlializer that would help to limit the input on the front-end
Upon request # gabn88, as you may know by now, with DRF 3.0 and above, there is no easy solution.
Even IF you do manage to figure out a solution, it won't be pretty and will most likely fail on subsequent versions of DRF as it will override a bunch of DRF source which will have changed by then.
I forget the exact implementation I used, but the idea is to create 2 fields on the serializer, one your normal serializer field (lets say PrimaryKeyRelatedField etc...), and another field a serializer method field, which the results will be swapped under certain cases (such as based on the request, the request user, or whatever). This would be done on the serializers constructor (ie: init)
Your serializer method field will return a custom query that you want.
You will pop and/or swap these fields results, so that the results of your serializer method field will be assigned to the normal/default serializer field (PrimaryKeyRelatedField etc...) accordingly. That way you always deal with that one key (your default field) while the other key remains transparent within your application.
Along with this info, all you really need is to modify this: http://www.django-rest-framework.org/api-guide/serializers/#dynamically-modifying-fields
I wrote a custom CustomQueryHyperlinkedRelatedField class to generalize this behavior:
class CustomQueryHyperlinkedRelatedField(serializers.HyperlinkedRelatedField):
def __init__(self, view_name=None, **kwargs):
self.custom_query = kwargs.pop('custom_query', None)
super(CustomQueryHyperlinkedRelatedField, self).__init__(view_name, **kwargs)
def get_queryset(self):
if self.custom_query and callable(self.custom_query):
qry = self.custom_query()(self)
else:
qry = super(CustomQueryHyperlinkedRelatedField, self).get_queryset()
return qry
#property
def choices(self):
qry = self.get_queryset()
return OrderedDict([
(
six.text_type(self.to_representation(item)),
six.text_type(item)
)
for item in qry
])
Usage:
class MySerializer(serializers.HyperlinkedModelSerializer):
....
somefield = CustomQueryHyperlinkedRelatedField(view_name='someview-detail',
queryset=SomeModel.objects.none(),
custom_query=lambda: MySerializer.some_custom_query)
#staticmethod
def some_custom_query(field):
return SomeModel.objects.filter(somefield=field.context['request'].user.email)
...
I did the following:
class MyModelSerializer(serializers.ModelSerializer):
myForeignKeyFieldName = MyForeignModel.objects.all()
def get_fields(self, *args, **kwargs):
fields = super(MyModelSerializer, self).get_fields()
qs = MyModel.objects.filter(room=self.instance.id)
fields['myForeignKeyFieldName'].queryset = qs
return fields
I looked for a solution where I can set the queryset upon creation of the field and don't have to add a separate field class. This is what I came up with:
class PurchaseSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Purchase
fields = ["purchaser"]
def get_purchaser_queryset(self):
user = self.context["request"].user
return Purchase.objects.filter(purchaser=user)
def get_extra_kwargs(self):
kwargs = super().get_extra_kwargs()
kwargs["purchaser"] = {"queryset": self.get_purchaser_queryset()}
return kwargs
The main issue for tracking suggestions regarding this seems to be drf#1985.
Here's a re-usable generic serializer field that can be used instead of defining a custom field for every use case.
class DynamicPrimaryKeyRelatedField(serializers.PrimaryKeyRelatedField):
"""A PrimaryKeyRelatedField with ability to set queryset at runtime.
Pass a function in the `queryset_fn` kwarg. It will be passed the serializer `context`.
The function should return a queryset.
"""
def __init__(self, queryset_fn=None, **kwargs):
assert queryset_fn is not None, "The `queryset_fn` argument is required."
self.queryset_fn = queryset_fn
super().__init__(**kwargs)
def get_queryset(self):
return self.queryset_fn(context=self.context)
Usage:
class MySerializer(serializers.ModelSerializer):
my_models = DynamicPrimaryKeyRelatedField(
queryset_fn=lambda context: MyModel.objects.visible_to_user(context["request"].user)
)
# ...
Same works for serializers.SlugRelatedField.

Categories