I have an exam model and I am trying to add a form to add multiple Quiz instance in the parent model. I am getting the following error
raise ValueError(
ValueError: Cannot assign "": "exam_quiz.quiz" must be a "Quiz" instance.
class ExamQuizAdminForm(forms.ModelForm):
class Meta:
model = exam_quiz
exclude = ['registrationDate','examdate']
quiz = forms.ModelMultipleChoiceField(
queryset=Quiz.objects.all(),
required=False,
label=_("Quiz"),
widget=FilteredSelectMultiple(
verbose_name=_("Quiz"),
is_stacked=False))
def __init__(self, *args, **kwargs):
super(ExamQuizAdminForm, self).__init__(*args, **kwargs)
if self.instance.pk:
self.fields['quiz'].initial = \
self.instance.quiz
def save(self, commit=True):
exam_quiz = super(ExamQuizAdminForm, self).save(commit=False)
exam_quiz.save()
exam_quiz.quiz_set.set(self.cleaned_data['Quiz'])
self.save_m2m()
return exam_quiz
class ExamQuizAdmin(admin.ModelAdmin):
form = ExamQuizAdminForm
list_display = ('examname','month','year')
Assuming exam_quiz.quiz is a m2m field...
I don't think you need to override the save function in this case. Use the save_m2m() function if you want to, say, add additional non form data, and then save both the normal and m2m data from the form. According to the docs
"Calling save_m2m() is only required if you use save(commit=False).
When you use a save() on a form, all data – including many-to-many
data – is saved without the need for any additional method calls."
Here, however, it looks like the only change you're making is adding the m2m data, which the normal save can handle.
Also, you might try making the Quiz lowercase, eg,
exam_quiz.quiz_set.set(self.cleaned_data['quiz'])
as I don't think 'label' or 'verbose_name' affect the HTML field name.
Related
I have a modelform which generates a series of checkboxes to add children to a parent part. I have created a validator that checks for cyclic relationships. I'm currently getting errors however because "proposed_child" is a queryset containing however many values the user has selected. How do I have this validator run on each object in that queryset?
def __init__(self, qs, part, *args, **kwargs):
super(AddPartToAssemblyForm, self).__init__(*args, **kwargs)
self.part = part
self.fields['children'] = forms.ModelMultipleChoiceField(
queryset=qs,
widget=forms.CheckboxSelectMultiple
)
def clean_children(self):
proposed_child = self.cleaned_data['children']
part = self.part
validate_acyclic_relationship(part, proposed_child)
class Meta:
model = Part
fields = ['children']
I figured it out, I changed proposed_child to proposed children and changed my validator so that it iterates through the queryset it receives and validates each object in turn.
Now I have working validator but if the data is valid the form isn't returning anything in form.cleaned_data['children']
--edit--
needed to return proposed_children at the end of clean_children
Given a serializer with a reference to a custom serializer:
class IndustryIdeaSerializer(serializers.ModelSerializer):
sub_industry = IndustrySerializer(many=False, read_only=True)
class Meta:
model = myModels.IdeaIndustry
fields = (
'id'
, 'sub_industry'
)
I am unable to save changes to this class when I post JSON like { sub_industry: 12 } or { sub_industry_id: 12 }
It does return the right JSON for displaying the data, and I wouldn't change it from that perspective. However changing it to:
class IndustryIdeaSerializer(serializers.ModelSerializer):
class Meta:
model = myModels.IdeaIndustry
fields = (
'id'
, 'sub_industry'
)
Gives me the save action (can persist with the simple JSON) I want BUT not the read action (doesn't return all the data associated with that foreign key)!
First am I missing something obvious? Is there a pattern to deal with behavior I am after - namely read and return the deep tree, but persist with just the Id's
This is for DRF 3.0. I just whipped this up this afternoon, I will follow up if I encounter any unforeseen problems (likewise, let me know if you spot anything wrong! I am fairly new to DRF)
class EnhancedPrimaryKeyRelatedField(serializers.PrimaryKeyRelatedField):
'''
This custom field extends the PrimaryKeyRelatedField
It overrides to_representation (which generates the data to be
serialized) to use a given serializer.
This allows other serializers to show nested data about a related
field, while still allowing the client to set relations by simply
passing an id.
To initialize, pass the queryset and serializer arguments.
The serializer argument should be a Serializer class.
If the serializer provides Meta.model (such as a ModelSerializer),
and you wish to use the queryset provided by that serializer, you may
omit the queryset argument.
e.g.
# without queryset
child_object = EnhancedPrimaryKeyRelatedField(
serializer=ChildObjectSerializer
)
# with queryset
child_object = EnhancedPrimaryKeyRelatedField(
queryset=models.ChildObject.objects.all(),
serializer=SomeSpecializedSerializer
)
'''
def __init__(self, *args, **kwargs):
assert 'serializer' in kwargs
self.serializer = kwargs['serializer']
del kwargs['serializer']
if 'queryset' not in kwargs:
# Catch any programmer errors
assert 'Meta' in self.serializer.__dict__
assert 'model' in self.serializer.Meta.__dict__
kwargs['queryset'] = self.serializer.Meta.model.objects.all()
super(serializers.PrimaryKeyRelatedField, self).__init__(*args, **kwargs)
def to_representation(self, data):
if hasattr(data.pk, 'all'): # are we dealing with a collection?
return self.serializer(data.pk.all(), many=True).data
elif hasattr(data, 'pk') and data.pk:
return self.serializer(self.queryset.get(pk=data.pk)).data
else:
return data.pk
There's nothing built in that handles this explicitly, but it's now come up a couple of times recently (e.g. here so perhaps we need to make it easier.
The work-around is to subclass PrimaryKeyRelatedField, which will handle setting the relation and override to_native to provide the full serialisation you're after.
I hope that helps.
I want my Django custom model field to set an attribute on the model instance.
I'm sure it's not working this way but here is an example:
class MyField(models.Field):
__metaclass__ = models.SubfieldBase
def __init__(self, *args, **kwargs):
super(MyField, self).__init__(*args, **kwargs)
model_instance = ????
setattr(model_instance, "extra_attribute", "It's working!")
class MyModel(models.Model):
my_field = MyField()
model_instance = MyModel.objects.get(pk=123)
print model_instance.extra_attribute # output: "It's working!"
Django's ForeignKey model field is doing a similar thing, so it is possible :P
I think ForeignKey field is using the contribute_to_class method.
You can create a special Proxy class that will replace the field. When getting or setting field value, you can use 'obj' attribute to access model instance. See this example:
class ObjectField(models.PositiveSmallIntegerField):
class ObjectProxy:
def __init__(self, field):
self.field = field
def __get__(self, obj, model):
if obj is None:
return self.field # this is required for initializing model field
value = obj.__dict__[self.field.name] # get actual field value
# ... here you can do something with value and model instance ("obj")
return value
def __set__(self, obj, value):
# same here
obj.__dict__[self.field.name] = value
def contribute_to_class(self, cls, name):
super().contribute_to_class(cls, name)
# set up our proxy instead of usual field
setattr(cls, name, ObjectField.ObjectProxy(self))
You do not have access to the model instance from inside your Field object, sorry. Django's ForeignKey accomplishes the foo_id thing by having separate name and attname fields, but the actual setting of foo_id = 123 is done the same way as all the other model fields, deep in the QuerySet code, without interacting with the field classes.
And conceptually, what you're trying to do is a bad idea - action-at-a-distance. What if adding a particular field could cause bugs in unrelated model functionality, say, if an attribute another field was expecting got overridden? It would be difficult to debug, to say the least. I don't know what your underlying goal is, but it should probably be done in model code, not a field class.
Here's a ModelField that does what you want:
https://gist.github.com/1987190
That's actually pretty old (like maybe pre-1.0, don't remember now), had to dust it off a bit - I'm not sure if it still works. But it's definitely doable, hopefullly this gives you an idea.
init is called when Django processes the Model Class, not the Model Instance. So, you can add the attribute to the Model Class (e.g. by using 'add_to_class' http://www.alrond.com/en/2008/may/03/monkey-patching-in-django/ ). To add the attribute to the instance you should override the init of the instance (but I think this is not your case).
How about
model_instance = SomeExtraModel.objects.get(pk=1456)
replacing 1456 with something that makes sense
I'm trying to learn Django and I've ran into some confusing points. I'm currently having trouble creating a movie using a form. The idea of the form is to give the user any field he'd like to fill out. Any field that the user fills out will be updated in its respective sql table (empty fields will be ignored). But, the form keeps giving me the error "Enter a list of values" when I submit the form. To address this, I thought stuffing the data from the form into a list and then returning that list would solve this.
The first idea was to override the clean() in my ModelForm. However, because the form fails the is_valid() check in my views, the cleaned_data variable in clean() doesn't contain anything. Next, I tried to override the to_python(). However, to_python() doesn't seem to be called.
If I put __metaclass__ = models.SubfieldBase in the respective model, I receive the runtime error
"TypeError: Error when calling the
metaclass bases
metaclass conflict: the metaclass of a derived class must be a
(non-strict) subclass of the
metaclasses of all its bases"
My approach doesn't seem to work. I'm not sure how to get around the 'Enter a list of values" error! Any advice?
Here is the relevant code (updated):
models.py
""" Idea:
A movie consists of many equipments, actors, and lighting techniques. It also has a rank for the particular movie, as well as a title.
A Theater consists of many movies.
A nation consists of many theaters.
"""
from django.db import models
from django.contrib.auth.models import User
class EquipmentModel(models.Model):
equip = models.CharField(max_length=20)
# user = models.ForeignKey(User)
class ActorModel(models.Model):
actor = models.CharField(max_length=20)
# user = models.ForeignKey(User)
class LightModel(models.Model):
light = models.CharField(max_length=20)
# user = models.ForeignKey(User)
class MovieModel(models.Model):
# __metaclass__ = models.SubfieldBase
rank = models.DecimalField(max_digits=5000, decimal_places=3)
title = models.CharField(max_length=20)
equipments = models.ManyToManyField(EquipmentModel, blank=True, null=True)
actors = models.ManyToManyField(ActorModel, blank=True, null=True)
lights = models.ManyToManyField(LightModel, blank=True, null=True)
class TheaterModel(models.Model):
movies = models.ForeignKey(MovieModel)
class NationModel(models.Model):
theaters = models.ForeignKey(TheaterModel)
=====================================
forms.py
"""
These Modelforms tie in the models from models.py
Users will be able to write to any of the fields in MovieModel when creating a movie.
Users may leave any field blank (empty fields should be ignored, ie: no updates to database).
"""
from django import forms
from models import MovieModel
from django.forms.widgets import Textarea
class MovieModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(MovieModelForm, self).__init__(*args, **kwargs)
self.fields["actors"].widget = Textarea()
self.fields["equipments"].widget = Textarea()
self.fields["lights"].widget = Textarea()
def clean_actors(self):
data = self.cleaned_data.get('actors')
print 'cleaning actors'
return [data]
class Meta:
model = MovieModel
=============================================
views.py
""" This will display the form used to create a MovieModel """
from django.shortcuts import render_to_response
from django.template import RequestContext
from forms import MovieModelForm
def add_movie(request):
if request.method == "POST":
form = MovieModelForm(request.POST)
if form.is_valid():
new_moviemodel = form.save()
return HttpResponseRedirect('/data/')
else:
form = MovieModelForm()
return render_to_response('add_movie_form.html', {form:form,}, context_instance=RequestContext(request))
The probable problem is that the list of values provided in the text area can not be normalized into a list of Models.
See the ModelMultipleChoiceField documentation.
The field is expecting a list of valid IDs, but is probably receiving a list of text values, which django has no way of converting to the actual model instances. The to_python will be failing within the form field, not within the form itself. Therefore, the values never even reach the form.
Is there something wrong with using the built in ModelMultipleChoiceField? It will provide the easiest approach, but will require your users to scan a list of available actors (I'm using the actors field as the example here).
Before I show an example of how I'd attempt to do what you want, I must ask; how do you want to handle actors that have been entered that don't yet exist in your database? You can either create them if they exist, or you can fail. You need to make a decision on this.
# only showing the actor example, you can use something like this for other fields too
class MovieModelForm(forms.ModelForm):
actors_list = fields.CharField(required=False, widget=forms.Textarea())
class Meta:
model = MovieModel
exclude = ('actors',)
def clean_actors_list(self):
data = self.cleaned_data
actors_list = data.get('actors_list', None)
if actors_list is not None:
for actor_name in actors_list.split(','):
try:
actor = Actor.objects.get(actor=actor_name)
except Actor.DoesNotExist:
if FAIL_ON_NOT_EXIST: # decide if you want this behaviour or to create it
raise forms.ValidationError('Actor %s does not exist' % actor_name)
else: # create it if it doesnt exist
Actor(actor=actor_name).save()
return actors_list
def save(self, commit=True):
mminstance = super(MovieModelForm, self).save(commit=commit)
actors_list = self.cleaned_data.get('actors_list', None)
if actors_list is not None:
for actor_name in actors_list.split(","):
actor = Actor.objects.get(actor=actor_name)
mminstance.actors.add(actor)
mminstance.save()
return mminstance
The above is all untested code, but something approaching this should work if you really want to use a Textarea for a ModelMultipleChoiceField. If you do go down this route, and you discover errors in my code above, please either edit my answer, or provide a comment so I can. Good luck.
Edit:
The other option is to create a field that understands a comma separated list of values, but behaves in a similar way to ModelMultipleChoiceField. Looking at the source code for ModelMultipleChoiceField, it inhertis from ModelChoiceField, which DOES allow you to define which value on the model is used to normalize.
## removed code because it's no longer relevant. See Last Edit ##
Edit:
Wow, I really should have checked the django trac to see if this was already fixed. It is. See the following ticket for information. Essentially, they've done the same thing I have. They've made ModelMutipleChoiceField respect the to_field_name argument. This is only applicable for django 1.3!
The problem is, the regular ModelMultipleChoiceField will see the comma separated string, and fail because it isn't a List or Tuple. So, our job becomes a little more difficult, because we have to change the string to a list or tuple, before the regular clean method can run.
class ModelCommaSeparatedChoiceField(ModelMultipleChoiceField):
widget = Textarea
def clean(self, value):
if value is not None:
value = [item.strip() for item in value.split(",")] # remove padding
return super(ModelCommaSeparatedChoiceField, self).clean(value)
So, now your form should look like this:
class MovieModelForm(forms.ModelForm):
actors = ModelCommaSeparatedChoiceField(
required=False,
queryset=Actor.objects.filter(),
to_field_name='actor')
equipments = ModelCommaSeparatedChoiceField(
required=False,
queryset=Equipment.objects.filter(),
to_field_name='equip')
lights = ModelCommaSeparatedChoiceField(
required=False,
queryset=Light.objects.filter(),
to_field_name='light')
class Meta:
model = MovieModel
to_python AFAIK is a method for fields, not forms.
clean() occurs after individual field cleaning, so your ModelMultipleChoiceFields clean() methods are raising validation errors and thus cleaned_data does not contain anything.
You haven't provided examples for what kind of data is being input, but the answer lies in form field cleaning.
http://docs.djangoproject.com/en/dev/ref/forms/validation/#cleaning-a-specific-field-attribute
You need to write validation specific to that field that either returns the correct data in the format your field is expecting, or raises a ValidationError so your view can re-render the form with error messages.
update: You're probably missing the ModelForm __init__ -- see if that fixes it.
class MovieModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(MovieModelForm, self).__init__(*args, **kwargs)
self.fields["actors"].widget = Textarea()
def clean_actors(self):
data = self.cleaned_data.get('actors')
# validate incoming data. Convert the raw incoming string
# to a list of ids this field is expecting.
# if invalid, raise forms.ValidationError("Error MSG")
return data.split(',') # just an example if data was '1,3,4'
I have two models related by a foreign key:
# models.py
class TestSource(models.Model):
name = models.CharField(max_length=100)
class TestModel(models.Model):
name = models.CharField(max_length=100)
attribution = models.ForeignKey(TestSource, null=True)
By default, a django ModelForm will present this as a <select> with <option>s; however I would prefer that this function as a free form input, <input type="text"/>, and behind the scenes get or create the necessary TestSource object and then relate it to the TestModel object.
I have tried to define a custom ModelForm and Field to accomplish this:
# forms.py
class TestField(forms.TextInput):
def to_python(self, value):
return TestSource.objects.get_or_create(name=value)
class TestForm(ModelForm):
class Meta:
model=TestModel
widgets = {
'attribution' : TestField(attrs={'maxlength':'100'}),
}
Unfortunately, I am getting: invalid literal for int() with base 10: 'test3' when attempting to check is_valid on the submitted form. Where am I going wrong? Is their and easier way to accomplish this?
Something like this should work:
class TestForm(ModelForm):
attribution = forms.CharField(max_length=100)
def save(self, commit=True):
attribution_name = self.cleaned_data['attribution']
attribution = TestSource.objects.get_or_create(name=attribution_name)[0] # returns (instance, <created?-boolean>)
self.instance.attribution = attribution
return super(TestForm, self).save(commit)
class Meta:
model=TestModel
exclude = ('attribution')
There are a few problems here.
Firstly, you have defined a field, not a widget, so you can't use it in the widgets dictionary. You'll need to override the field declaration at the top level of the form.
Secondly get_or_create returns two values: the object retrieved or created, and a boolean to show whether or not it was created. You really just want to return the first of those values from your to_python method.
I'm not sure if either of those caused your actual error though. You need to post the actual traceback for us to be sure.
TestForm.attribution expects int value - key to TestSource model.
Maybe this version of the model will be more convenient for you:
class TestSource(models.Model):
name = models.CharField(max_length=100, primary_key=True)
Taken from:
How to make a modelform editable foreign key field in a django template?
class CompanyForm(forms.ModelForm):
s_address = forms.CharField(label='Address', max_length=500, required=False)
def __init__(self, *args, **kwargs):
super(CompanyForm, self).__init__(*args, **kwargs)
try:
self.fields['s_address'].initial = self.instance.address.address1
except ObjectDoesNotExist:
self.fields['s_address'].initial = 'looks like no instance was passed in'
def save(self, commit=True):
model = super(CompanyForm, self).save(commit=False)
saddr = self.cleaned_data['s_address']
if saddr:
if model.address:
model.address.address1 = saddr
model.address.save()
else:
model.address = Address.objects.create(address1=saddr)
# or you can try to look for appropriate address in Address table first
# try:
# model.address = Address.objects.get(address1=saddr)
# except Address.DoesNotExist:
# model.address = Address.objects.create(address1=saddr)
if commit:
model.save()
return model
class Meta:
exclude = ('address',) # exclude form own address field
This version sets the initial data of the s_address field as the FK from self, during init , that way, if you pass an instance to the form it will load the FK in your char-field - I added a try and except to avoid an ObjectDoesNotExist error so that it worked with or without data being passed to the form.
Although, I would love to know if there is a simpler built in Django override.