Django admin edit inline objects on form clean - python

I have following model:
class ProjectBudget(models.Model):
It has a form and inline:
class ProjectBudgetAdmin(admin.ModelAdmin):
form = ProjectBudgetForm
inlines= [ProjectSpentsInline]
class ProjectSpentsInline(admin.TabularInline):
model = ProjectSpents
On a form i have a field file. From this file i want populate inline object ProjectSpents values:
class ProjectBudgetForm(ModelForm):
file = forms.FileField
def clean_file(self):
parse file then populate inline model objects ProjectSpents....
The problem is that i want to edit inline values of ProjectSpents from clean_file, but there is a problem, because queryset is already fullfilled before clean_file and new inline values are not
shoiwing. Maybe there is another solution?

Try overriding the model admin method save_formset. You can iterate through the formset instances to delete the existing inlines, then access form.cleaned_data['file'] and create the new related instances.

Related

How to add link fields to serializer dynamically

I would like to create a general serializer for a ManyToMany link, which will include linked models data.
from rest_framework import serializers
def get_model_serializer(model, fields_to_serialize):
class BaseSerializer(serializers.ModelSerializer):
class Meta:
model = model
fields = fields_to_serialize
return BaseSerializer
def get_many_to_many_serializer(many_to_many_model, first_model, second_model, fields_to_serialize)
serializer = get_model_serializer(many_to_many_model, fields_to_serialize)
class BaseSerializer(serializer):
pass
# only when I directly set the attributes upon class definition it works
#attendee = get_model_serializer(first_model)()
#operation = get_model_serializer(second_model)()
# This does not work
setattr(BaseSerializer, first_model.__name__.lower(), get_model_serializer(first_model)())
setattr(BaseSerializer, second_model.__name__.lower(), get_model_serializer(second_model)())
#Or this
#setattr(BaseSerializer, 'operation', get_model_serializer(first_model)())
#setattr(BaseSerializer, 'attendee', get_model_serializer(second_model)())
return BaseSerializer
The problem is, that when I set the attribute using setattr, the linked models are not serialized. I guess there is some magic upon class creation or whatever?
Any ideas, how can I go around this?
You can use the three argument form of type to dynamically create new types/classes
type('M2MSerializer', (serializer, ), {
first_model.__name__.lower(): get_model_serializer(first_model)(),
first_model.__name__.lower(): get_model_serializer(first_model)()
})

unit testing django ModelFormset clean methods

What is the best way to unit test the validation/clean part of a Django ModelFormset? My formset has a clean method that does some validation based on the values of its forms and I want to have a unit test for it. Generified code look like this:
class AForm(ModelForm):
a = ChoiceField(choices=CHOICES)
b = FloatField()
def __init__(self, *args, **kwargs):
super(AForm, self).__init__(*args, **kwargs)
class Meta:
model = AModel
fields =['a', 'b']
class AFormset(BaseInlineFormSet):
def clean(self):
# Some logic to validate relationships between the forms
But while testing the form is trivial: form = AForm(formdata) and then verifying its validity or errors based on the data. I'm having trouble writing tests for the formset.
I've tried:
formset = AFormset()
And using modelformset_factory
Formset = modelformset_factory(AModel, AForm, formset=AFormset, fields=('a', 'b'))
formset = Formset(formdata)
with various combinations of arguments and mocks (instance, queryset). But I always get errors related to model foreign or primary keys. One example:
Error Traceback (most recent call last):
File "/tests/test_forms.py", line 62, in test_validation formset = Formset(formdata)
File "lib/python2.7/site-packages/django/forms/models.py", line 853, in __init__
self.instance = self.fk.rel.to()
AttributeError: 'AFormset' object has no attribute 'fk'
What am I missing? Is there an easier way to instantiate a formset with a dictionary of data and have it run its clean method? Should I just test the view that the form is used in? (In the views I'm using the form and formset with the Django Extra Views package)
The problem is not with your test, but has to do with an incompatibility between your base formset class and the formset factory.
Your AFormset class inherits from BaseInlineFormSet. That class expects an fk property to exist, which should determine the foreign key of the form model to the object to which it is "inline". That property is created by the inlineformset_factory function. However, you are using modelformset_factory to construct your concrete formset class; this does not set that fk property.
You should either use inlineformset_factory (and pass in the parent model), or change your formset class to inherit from BaseModelFormSet if it is not actually inline.

Making disabled field in ModelForm subclass

I got a model Layout in my Django app with the following fields:
meta_layout - ForeignKey on model MetaLayout
name - CharField
edited - DateTimeField
is_active - BooleanField
And I have two views using this model - one called NewLayout and other EditLayout each subclassing standard CreateView and UpdateView accordingly. In EditLayout view I want to use some special form that looks the same as form used in NewLayout (which is simply plain ModelForm for this model) but has meta_layout select field displayed with attribute disabled="disabled" (e.d. user can choose meta_layout for each Layout only once - while creating it). Ok, I can create custom ModelForm where widget for meta_layout field has the desired attribute, but the problem is actually that when such attribute set on form field it will not send any values with request - so my validation fails trying to check value for this field and select element does not support "readonly" attribute which will would be just fine here.
I found some really ugly hack to workaround this:
#Here is my Form:
class LayoutEditForm(forms.ModelForm):
meta_layout = forms.ModelChoiceField(
queryset=MetaLayout.objects.all(),
widget=forms.Select(attrs=dict(disabled='disabled')),
empty_label=None,
required=False) # if required=True validation will fail
# because value is not supplied in POST
class Meta:
fields = ('meta_layout', 'name', 'is_active')
model = Layout
class EditLayout(UpdateView):
...
# And one modified method from my View-class
def get_form_kwargs(self):
kwargs = super(EditLayout, self).get_form_kwargs()
# actually POST parameters
if kwargs.has_key('data'):
# can't change QueryDict itself - it's immutable
data = dict(self.request.POST.items())
# emulate POST params from ModelChoiceField
data['meta_layout'] = u'%d' % self.object.meta_layout.id
kwargs['data'] = data
return kwargs
But I believe that it's non-Django, non-Pythonic and not a good-programming-style-at-all of doing such simple thing. Can you suggest any better solution?
Edit:
Oh, I found much less ugly solution: added this in my form class:
def clean_meta_layout(self):
return self.instance.meta_layout
But I still open for suggestions) - may I missed something?

Where's the primary key of the object I want to update in django using modelform?

I'm using modelform in django to insert and update objects in my database, but when I try to update I cannot see the primary key/id of the object being updated:
My model:
class Category(models.Model):
name = models.CharField(max_length=20, db_index = True)
and my form:
class CategoryForm(ModelForm):
class Meta:
model = Category
fields = ['name']
and in my template I got:
{% csrf_token %}
{{ category_form.as_p }}
In my view I do
cat = Category.objects.get(pk = cat_id)
data['category_form'] = CategoryForm(instance = cat)
and pass the data to my template, which renders the form ok, but the id of the object I about to update is nowhere in the html source. How can the code then now what object to update?
I feel stupid asking this since it should be pretty basic, but I've followed all the tutorials and looked thru the django docs, googled and search this site without luck.
Thanks in advance.
Where is cat_id coming from in your view? I guess you receive it in url, like so:
url( r'categories/(\d+)/edit/', your_view, {} ),
in urls.py somewhere. Now in your view you can read it from appropriate view function argument:
def your_view( request, cat_id ):
Now you can obtain object with proper id, which you do here:
cat = Category.objects.get(pk = cat_id)
...and instantiate ModelForm passing it cat object if you want to edit existing object, or don't pass it, if you want an empty form for object creation.
The explanation for this can be found in the django docs here: http://docs.djangoproject.com/en/dev/topics/forms/modelforms/#the-save-method
While trying to update already saved entity you must provide an instance parameter when you recreate the form. Otherwise django will try to insert a new entity.
foo_form = FooForm(request.POST, instance=foo)
The primary key is an attribute called "id" on your instance object "cat". The form itself, and in your example represented by "cat_id". The Model form should takes care to track the primary key - all you should need to do is pass the resulting "request.POST" data back into CategoryForm, valid the data with is_valid() and then save it.
i.e.
form_with_post = CategoryForm(request.POST)
if form_with_post.is_valid():
form_with_post.save()
else:
... return the form_with_post through the context to display the errors
The ID doesn't need to be in the HTML, because you've passed the instance into the form object when you instantiate it. Django simply updates that instance when you do form.save().

When is the formfield() method called in Django?

This is a question on making custom fields in Django. I'm making a field called EditAreaField, which inherits from TextField. Here's what my code looks like:
class EditAreaField(models.TextField):
description = "A field for editing the HTML of a page"
def formfield(self, **kwargs):
defaults = {}
defaults['widget'] = EditArea() # setting a new widget
defaults.update(kwargs)
return super(EditAreaField, self).formfield(**defaults)
On the 5th line, I'm assigning a custom widget to this field. On line 6, I update the parameters.
The problem is, Django sends a parameter widget that's set to django.contrib.admin.widgets.AdminTextareaWidget, which overrides my EditArea() widget.
How can I change the value that Django is setting? Obviously I could just override their setting by switching lines 5 and 6, so my code looks like:
defaults.update(kwargs)
defaults['widget'] = EditArea() # override django here
But is that really the best way to do it?
As a side note, I couldn't find documentation on the formfield() function anywhere on Django's site: is it deprecated?
It looks like the formfield method is called by the ModelForm helper. According to the docs, the formfield method should include only a form_class attribute to point to the formfield class for this custom model field. This is a custom (or default) form field class, which is where the default widget is defined
from myapp.forms import MyCustomFormField
#create a custom model field
class EditAreaField(models.TextField):
def formfield(self, **kwargs):
defaults={'form_class': MyCustomFormField}#pass our custom field as form_class
defaults.update(kwargs)
return super(EditAreaField, self).formfield(**defaults)

Categories