I have a lesson model:
class Lesson(models.Model):
list_name = models.CharField(max_length=50, primary_key=True)
def __str__(self):
return self.list_name
A sound model:
class Sound(models.Model):
sound_hash = models.CharField(max_length=40, primary_key=True, editable=False)
lessons = models.ManyToManyField(Lesson, verbose_name="associated lessons", related_name="words")
And a test_pair model:
class TestPair(models.Model):
master_sound = models.ForeignKey(Sound, related_name="used_as_master")
user_sound = models.ForeignKey(Sound, related_name="used_as_user")
My form looks something like this:
class SoundTestPairForm(ModelForm):
user_sounds = forms.ModelMultipleChoiceField(Sound.objects.all())
class Meta:
model = TestPair
fields = ['master_sound']
def __init__(self, *args, **kwargs):
super(SoundTestPairForm, self).__init__(*args, **kwargs)
self.fields['master_sound'] = forms.CharField()
self.fields['master_sound'].widget.attrs['readonly'] = 'readonly'
def clean(self):
cleaned_data = super(SoundTestPairForm, self).clean()
if 'user_sounds' not in cleaned_data.keys():
raise forms.ValidationError('You must select at least one sound to be compared')
cleaned_data['master_sound'] = Sound.objects.get(pk=self.fields['master_sound'])
return cleaned_data
This is throwing a DoesNotExist error. The traceback points to this line: cleaned_data['master_sound'] = Sound.objects.get(pk=self.fields['master_sound'])
The local vars are as follows:
self
<SoundTestPairForm bound=True, valid=True, fields=(master_sound;associated_test;associated_lesson;user_sounds)>
cleaned_data
{'associated_lesson': u'pooooooooooooooooooooooop',
'associated_test': u'cats a',
'master_sound': u'ad27ec5e0d048ddbb17d0cef0c7b9d4406a2c33',
'user_sounds': [<Sound: Pants>]}
But when I go to python manage.py shell, and import my model:
Sound.objects.get(pk=u'ad27ec5e0d048ddbb17d0cef0c7b9d4406a2c33')
<Sound: oombah>
It hits the appropriate object.
This is something I've been dealing with for a long time, and it's super frustrating. I try to switch my logic around and ultimately it ends up throwing a DoesNotExist error, regardless of what I try.
Does anyone have any ideas?
I think you should replace:
Sound.objects.get(pk=self.fields['master_sound'])
with:
Sound.objects.get(pk=cleaned_data['master_sound'])
When the Form is valid, cleaned_data will include a key and value for all its fields and you can see that in your question as well (The local vars paragraph).
In general speaking you should do validation in the individual clean_<fieldname> methods. So:
def clean_master_sound(self):
master_sound_key = self.cleaned_data.get('master_sound')
try:
master_sound=Sound.objects.get(pk=master_sound_key)
except Sound.DoesNotExist:
raise forms.ValidationError("Sound with id: %s does not exist", master_sound_key)
return master_sound
Related
I have a model that has a choice field with choices loaded in the runtime.
from some_utils import get_currency_options
class Product(models.Model):
name = models.CharField(max_length=20)
currency = models.CharField(max_length=3, null=True, blank=True, default="USD", choices=[])
def clean(self):
# getting the currency options at execution time, options may vary at different times
currency_code_options = get_currency_options()
if self.currency and self.currency not in currency_code_options:
raise ValidationError({"currency": f"Invalid currency code {self.fb_check_currency}."})
super(Product, self).clean()
Please ignore the bad design here, it was defined like this since we need to integrate with a legacy system.
In the Django admin, I have a form like this
from some_utils import get_currency_options
class ProductAdminForm(ModelForm):
currency_choices = get_currency_options()
#staticmethod
def _values_list_to_options(values_list):
return [(val, val) for val in values_list]
def __init__(self, *args, **kwargs):
super(ProductAdminForm, self).__init__(*args, **kwargs)
self.fields["currency"] = ChoiceField(choices=self._values_list_to_options(self.currency_choices))
class ProductAdmin(admin.ModelAdmin):
form = ProductAdminForm
Now the problem is, when I go to Django admin and want to update the currency option, it fails to save with an error saying currency is not a valid option. I understand this is due to the choice list being empty, I tried to override the clean and clean_all method but it didn't work.
Which method does the admin update operation trigger? Is there a way I can use the get_currency_options method to load currency options to the validator so if my selection matches one of the value, it passes the validator?
I have the same error. I your case, you need overview the clean_field method in your model class. For example:
from some_utils import get_currency_options
class Product(models.Model):
name = models.CharField(max_length=20)
currency = models.CharField(max_length=3, null=True, blank=True, default="USD", choices=[])
def clean_fields(self, exclude=None):
exclude = ['currency']
super().clean_fiedls(exclude=exclude)
def clean(self):
self.validate_currency()
super().clean()
def validate_currency(self):
# getting the currency options at execution time, options may vary at different times
currency_code_options = get_currency_options()
if self.currency and self.currency not in currency_code_options:
raise ValidationError({"currency": f"Invalid currency code {self.fb_check_currency}."})
I am working on a school project using Django and Python.
Now I have created a website for using rest-apis. However, while testing I encountered an error that I simply can't seem to be able to solve. It occurs whenever I try to create a movie with POSTMAN using the api.I get the following error over and over again and I can't find what is wrong.
Error message:
raise TypeError("%s() got an unexpected keyword argument '%s'" % (cls.__name__, kwarg))
TypeError: Movie() got an unexpected keyword argument 'actors'
My serializer
class MovieSerializer(serializers.ModelSerializer):
# Queryset gets all the data from the Actor model as specified with objects.all()
actor_pks = serializers.PrimaryKeyRelatedField(queryset=Actor.objects.all(), source='actors', write_only=True,
label='Actors', many=True)
rent_pks = serializers.PrimaryKeyRelatedField(source='rent', read_only=True, label='Rent')
# Change image-options to allow post/put/patch without an image
image = serializers.ImageField(allow_null=True, required=False)
def __init__(self, *args, **kwargs):
# Get additional parameters from constructor
depth = kwargs.pop('depth', None)
fields = kwargs.pop('fields', None)
# Add diffrent pks to fields if field is not None from constructor
fields.append('actor_pks') if fields is not None else None
fields.append('rent_pks') if fields is not None else None
fields.append('image') if fields is not None else None
# Overwrite meta tags
self.Meta.depth = depth if depth is not None else 1
self.Meta.fields = fields if fields is not None else '__all__'
# Call super-constructor
super(MovieSerializer, self).__init__(*args, **kwargs)
class Meta:
model = Movie
My Movie Model:
class Movie(models.Model):
"""
Movie-Model. Many-to-Many relation with Rent.
"""
title = models.CharField(null=False, max_length=50)
publishdate = models.CharField(null=False, max_length=20)
genre = models.CharField(null=True, max_length=50)
is_borrowed = models.BooleanField(null=False, default=False)
image = models.ImageField(null=False, upload_to='movies', default='noimage.png')
rent = models.ManyToManyField(Rent, blank=False)
actor = models.ForeignKey(Actor, null=False, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return '{}'.format(self.title)
def save(self, *args, **kwargs):
if not self.image:
if self.id:
self.image = Movie.objects.get(pk=self.id).image
else:
self.image = 'noimage.png'
super().save(*args, **kwargs)
I would be glad for any help with this problem. I have been working on it quite a while now and I am simply to dumb to find a solution for this.
Thanks for your help lads.
After a lot of time I was able to find the the answer to the question.
In the models in my Movie model, I had the following field implemented.
actor = models.ForeignKey(Actor, null=False, on_delete=models.CASCADE)
Turns out I had it's relation set wrong and therefor the serializer threw the error over and over again, since he was not able to make the connection, even if I changed the model field to "actors".
The solution was to turn the actor = models.ForeignKey to the following:
actor = models.ManyToManyField(Actor, related_name='movie_list', blank=False)
As well as change the actor_pks variable to the following:
actor_pks = serializers.PrimaryKeyRelatedField(queryset=Actor.objects.all(), source='actor', write_only=True, label='Actors', many=True)
I know how to update some field of ForeignKey. For example when I want change last_modified field every time if Configuration or SomeOtherImportantClass is changed:
class Configuration(models.Model):
title = models.TextField()
last_modified = models.DateTimeField(auto_now=True)
class SomeOtherImportantClass(models.Model):
conf = models.ForeignKey(Configuration)
important_number = models.IntegerField()
def save(self, *args, **kwargs):
conf.last_modified = timezone.now() # I'm not sure if it is necessary
conf.save()
return super().save(*args, **kwargs)
but in my real situation the Cofiguration model is a ForeignKey for more than 30 other models. In each of them I want to update configuration.last_modified field for every change performed on them or when another model (which has ForeignKey to some model which has ForeignKey do Configuration) is changed. So it looks like that:
class Configuration(models.Model):
title = models.TextField()
last_modified = models.DateTimeField(auto_now=True)
class A(models.Model):
conf = models.ForeignKey(Configuration) # conf.last_modified must be updated on every change on A model object.
class B(models.Model):
conf = models.ForeignKey(Configuration) # same
...
class Z(models.Model):
conf = models.ForeignKey(Configuration) # same
class AA(models.Model):
some_field = models.TextField()
a = models.ForeignKey(A)
...
class ZZ(models.Model)
some_field = models.TextField()
z = models.ForeignKey(Z)
so even if AA object field "some_field" is changed I want to update last_modified Configuration field. Is there any recursion way to declare it once in Configuration or somewhere else?
UPDATE: Great-granchilds like AAA and AAAA classes can exist too.
Use abstract base classes as explained in the docs. For A-Z it's quite easy:
class ConfigurationChild(Model):
conf = ForeignKey(Configuration)
class Meta:
abstract = True
def save(self):
self.conf.last_modified = ...
self.conf.save()
super().save()
class A(ConfigurationChild):
# other fields, without conf
For the grand-children it's a bit more complex because then don't have a reference to conf directly. Set an attribute on the base class that you populate on each child class:
class ConfigurationDescendant(Model):
conf_handle = None
class Meta:
abstract = True
def get_conf(self):
if not self.conf_handle:
return None # or raise an error
parent = getattr(self, self.conf_handle)
if isinstance(parent, ConfigurationDescendant):
return parent.get_conf() # recursion
else:
# reached `ConfigurationChild` class, might want to check this
return parent.conf if parent else None
def save(self):
conf = self.get_conf()
# you might want to handle the case that the attribute is None
if conf:
conf.last_modified = ...
conf.save()
super().save()
class AA(ConfigurationDescendant):
conf_handle = 'a'
a = ForeignKey(A)
class AAA(ConfigurationDescendant):
conf_handle = 'aa'
aa = ForeignKey(AA)
The above code will handle the case when the chain breaks because conf_handle is missing on one of the parents. In which case None is returned and nothing happens. I'm not checking if the handle is set wrongly (i.e. not pointing in the right direction towards the parent Configuration), that will raise an exception which you probably want so you can catch mistakes.
I have a 3-level Test model I want to present as nested formsets. Each Test has multiple Results, and each Result can have multiple Lines. I am following Yergler's method for creating nested formsets, along with this SO question that updates Yergler's code for more recent Django version (I'm on 1.4)
I am running into trouble because I want to use FormSet's "extra" parameter to include an extra Line in the formset. The ForeignKey for each Line must point to the Result that the Line belongs to, but cannot be changed by the user, so I use a HiddenInput field to contain the Result in each of the FormSet's Lines.
This leads to "missing required field" validation errors because the result field is always filled out (in add_fields), but the text and severity may not (if the user chose not to enter another line). I do not know the correct way to handle this situation. I think that I don't need to include the initial result value in add_fields, and that there must be a better way that actually works.
Update below towards bottom of this question
I will gladly add more detail if necessary.
The code of my custom formset:
LineFormSet = modelformset_factory(
Line,
form=LineForm,
formset=BaseLineFormSet,
extra=1)
class BaseResultFormSet(BaseInlineFormSet):
def __init__(self, *args, **kwargs):
super(BaseResultFormSet, self).__init__(*args, **kwargs)
def is_valid(self):
result = super(BaseResultFormSet, self).is_valid()
for form in self.forms:
if hasattr(form, 'nested'):
for n in form.nested:
n.data = form.data
if form.is_bound:
n.is_bound = True
for nform in n:
nform.data = form.data
if form.is_bound:
nform.is_bound = True
# make sure each nested formset is valid as well
result = result and n.is_valid()
return result
def save_all(self, commit=True):
objects = self.save(commit=False)
if commit:
for o in objects:
o.save()
if not commit:
self.save_m2m()
for form in set(self.initial_forms + self.saved_forms):
for nested in form.nested:
nested.save(commit=commit)
def add_fields(self, form, index):
# Call super's first
super(BaseResultFormSet, self).add_fields(form, index)
try:
instance = self.get_queryset()[index]
pk_value = instance.pk
except IndexError:
instance=None
pk_value = hash(form.prefix)
q = Line.objects.filter(result=pk_value)
form.nested = [
LineFormSet(
queryset = q, #data=self.data, instance = instance, prefix = 'LINES_%s' % pk_value)]
prefix = 'lines-%s' % pk_value,
initial = [
{'result': instance,}
]
)]
Test Model
class Test(models.Model):
id = models.AutoField(primary_key=True, blank=False, null=False)
attempt = models.ForeignKey(Attempt, blank=False, null=False)
alarm = models.ForeignKey(Alarm, blank=False, null=False)
trigger = models.CharField(max_length=64)
tested = models.BooleanField(blank=False, default=True)
Result Model
class Result(models.Model):
id = models.AutoField(primary_key=True)
test = models.ForeignKey(Test)
location = models.CharField(max_length=16, choices=locations)
was_audible = models.CharField('Audible?', max_length=8, choices=audible, default=None, blank=True)
Line Model
class Line(models.Model):
id = models.AutoField(primary_key=True)
result = models.ForeignKey(Result, blank=False, null=False)
text = models.CharField(max_length=64)
severity = models.CharField(max_length=4, choices=severities, default=None)
Update
Last night I added this to my LineForm(ModelForm) class:
def save(self, commit=True):
saved_instance = None
if not(len(self.changed_data) == 1 and 'result' in self.changed_data):
saved_instance = super(LineForm, self).save(commit=commit)
return saved_instance
It ignores the requests to save if only the result (a HiddenInput) is filled out. I haven't run into any problems with this approach yet, but I haven't tried adding new forms.
When I used extra on formsets in similar situation I ended up having to include all the required fields from the model in the form, as HiddenInputs. A bit ugly but it worked, curious if anyone has a hack-around.
edit
I was confused when I wrote above, I'd just been working on formsets using extra with initial to pre-fill the extra forms and also I hadn't fully got all the details of your questions.
If I understand correctly, where you instantiate the LineFormSets in add_fields each of those will point to the same Result instance?
In this case you don't really want to supply result in initial due to the problems you're having. Instead you could remove that field from the LineForm model-form altogether and customise the LineFormSet class something like:
class LineFormSet(forms.BaseModelFormSet):
# whatever other code you have in it already
# ...
# ...
def __init__(self, result, *args, **kwargs):
super(LineFormSet, self).__init__(*args, **kwargs)
self.result = result
def save_new(self, form, commit=True):
instance = form.save(commit=False)
instance.result = self.result
if commit:
instance.save()
return instance
def save_existing(self, form, instance, commit=True):
return self.save_new(form, commit)
(this should be ok in Django 1.3 and 1.4, not sure other versions)
so the relevant part of your add_fields method would look like:
form.nested = [
LineFormSet(
result = instance,
queryset = q, #data=self.data, instance = instance, prefix = 'LINES_%s' % pk_value)]
prefix = 'lines-%s' % pk_value,
)]
I am trying to use the ModelForm to add my data. It is working well, except that the ForeignKey dropdown list is showing all values and I only want it to display the values that a pertinent for the logged in user.
Here is my model for ExcludedDate, the record I want to add:
class ExcludedDate(models.Model):
date = models.DateTimeField()
reason = models.CharField(max_length=50)
user = models.ForeignKey(User)
category = models.ForeignKey(Category)
recurring = models.ForeignKey(RecurringExclusion)
def __unicode__(self):
return self.reason
Here is the model for the category, which is the table containing the relationship that I'd like to limit by user:
class Category(models.Model):
name = models.CharField(max_length=50)
user = models.ForeignKey(User, unique=False)
def __unicode__(self):
return self.name
And finally, the form code:
class ExcludedDateForm(ModelForm):
class Meta:
model = models.ExcludedDate
exclude = ('user', 'recurring',)
How do I get the form to display only the subset of categories where category.user equals the logged in user?
You can customize your form in init
class ExcludedDateForm(ModelForm):
class Meta:
model = models.ExcludedDate
exclude = ('user', 'recurring',)
def __init__(self, user=None, **kwargs):
super(ExcludedDateForm, self).__init__(**kwargs)
if user:
self.fields['category'].queryset = models.Category.objects.filter(user=user)
And in views, when constructing your form, besides the standard form params, you'll specify also the current user:
form = ExcludedDateForm(user=request.user)
Here example:
models.py
class someData(models.Model):
name = models.CharField(max_length=100,verbose_name="some value")
class testKey(models.Model):
name = models.CharField(max_length=100,verbose_name="some value")
tst = models.ForeignKey(someData)
class testForm(forms.ModelForm):
class Meta:
model = testKey
views.py
...
....
....
mform = testForm()
mform.fields["tst"] = models.forms.ModelMultipleChoiceField(queryset=someData.objects.filter(name__icontains="1"))
...
...
Or u can try something like this:
class testForm(forms.ModelForm):
class Meta:
model = testKey
def __init__(self,*args,**kwargs):
super (testForm,self ).__init__(*args,**kwargs)
self.fields['tst'].queryset = someData.objects.filter(name__icontains="1")
I know this is old; but its one of the first Google search results so I thought I would add how I found to do it.
class CustomModelFilter(forms.ModelChoiceField):
def label_from_instance(self, obj):
return "%s %s" % (obj.column1, obj.column2)
class CustomForm(ModelForm):
model_to_filter = CustomModelFilter(queryset=CustomModel.objects.filter(active=1))
class Meta:
model = CustomModel
fields = ['model_to_filter', 'field1', 'field2']
Where 'model_to_filter' is a ForiegnKey of the "CustomModel" model
Why I like this method:
in the "CustomModelFilter" you can also change the default way that the Model object is displayed in the ChoiceField that is created, as I've done above.
is the best answer:
BookDemoForm.base_fields['location'] = forms.ModelChoiceField(widget=forms.Select(attrs={'class': 'form-control select2'}),queryset=Location.objects.filter(location_for__fuel=True))