Over-Ride __init__: Extending Forms (Python/Django) - python

Struggling to figure out how to Over-Ride the __init__() method in my Django Form to include additional values from the database. I have a group of photographers that I am trying to list as a form option for the user. Afterwards, the user's photographer selection will be added (along with other information) to the database as an instantiation of a new model.
This is a continuation, or elaboration, of my other Current Question. #Rob Osborne has given me some great advice helping me understand how to extend BaseForm, but I still cannot get my code to execute. The linked question lists my models, form, and views, if you are interested. While I understand that using ModelForm is easier and more documented, I must use BaseForm in this instance.
Here is what I have:
class AForm(BaseForm):
def __init__(self, data=None, files=None, instance=None, auto_id='id_%s',
prefix=None, initial=None, error_class=ErrorList,
label_suffix=':', empty_permitted=False):
self.instance = instance
object_data = self.instance.fields_dict()
self.declared_fields = SortedDict()
self.base_fields = fields_for_a(self.instance)
BaseForm.__init__(self, data, files, auto_id, prefix, object_data,
error_class, label_suffix, empty_permitted)
self.fields['photographer'].queryset = Photographer.objects.all()
def save(self, commit=True):
if not commit:
raise NotImplementedError("AForm.save must commit it's changes.")
if self.errors:
raise ValueError(_(u"The Form could not be updated because the data didn't validate."))
cleaned_data = self.cleaned_data
# save fieldvalues for self.instance
fields = field_list(self.instance)
for field in fields:
if field.enable_wysiwyg:
value = unicode(strip(cleaned_data[field.name]))
else:
value = unicode(cleaned_data[field.name])
Using the above code results in a KeyError at 'photographer'.
I appreciate any ideas / comments on how to resolve this KeyError so that I can get the photographer values into my form. Thank you!
EDIT:
Trying to use super, as recommended by #supervacuo, but still getting a KeyError at photographer as before:
class AForm(BaseForm):
def __init__(self, data=None, files=None, instance=None, auto_id='id_%s',
prefix=None, initial=None, error_class=ErrorList,
label_suffix=':', empty_permitted=False):
super(AForm, self).__init__(data, files, auto_id, prefix, object_data, error_class, label_suffix, empty_permitted)
self.fields['photographer'].queryset = Photographer.objects.all()
What could I be missing that is generating the KeyError? Thanks for any advice.
EDIT 2: adding fields_dict()
from models.py
class A(models.Model):
category = models.ForeignKey(Category)
user = models.ForeignKey(User)
def fields_dict(self):
fields_dict = {}
fields_dict['title'] = self.title
for key, value in self.fields():
fields_dict[key.name] = value.value
return fields_dict
Thanks for any advice.
EDIT 3: (edited class AForm above in the initial question as well, to include more information)
def fields_for_a(instance):
fields_dict = SortedDict()
fields = field_list(instance)
for field in fields:
if field.field_type == Field.BOOLEAN_FIELD:
fields_dict[field.name] = forms.BooleanField(label=field.label, required=False, help_text=field.help_text)
elif field.field_type == Field.CHAR_FIELD:
widget = forms.TextInput
fields_dict[field.name] = forms.CharField(label=field.label, required=field.required, max_length=field.max_length, help_text=field.help_text, widget=widget)
fields_dict[field.name] = field_type(label=field.label,
required=field.required,
help_text=field.help_text,
max_length=field.max_length,
widget=widget)
return fields_dict
EDIT 4: def fields(self). from models.py:
def fields(self):
fields_list = []
fields = list(self.category.field_set.all())
fields += list(Field.objects.filter(category=None))
for field in fields:
try:
fields_list.append((field, field.fieldvalue_set.get(ad=self),))
except FieldValue.DoesNotExist:
pass # If no value is associated with that field, skip it.
return fields_list
def field(self, name):
if name == 'title':
return self.title
else:
return FieldValue.objects.get(field__name=name, ad=self).value

That GitHub link should've been the first thing in your question.
The django-classifieds application has an entire system of dynamic fields (based on the Field and FieldValue models) which is why you're having trouble. If you don't fully understand this aspect of django-classifieds, I recommend you base your project on something else instead.
Looking down the list of FIELD_CHOICES in django-classified's models.py, you can't use this database-driven field system to define relationsips — so there's no dynamic per-category ForeignKey field!
The alternative would be to add a photographer field on your A model (any particular reason you've renamed it from Ad?), as it seems you have done based on your other question. To go the rest of the distance, however, you'd need to edit the fields_dict() method like so:
def fields_dict(self):
fields_dict = {}
fields_dict['title'] = self.title
fields_dict['photographer'] = self.photographer
for key, value in self.fields():
fields_dict[key.name] = value.value
return fields_dict

Your call to BaseForm.__init__ seems wrong; you should be using super(), like so
class AForm(BaseForm):
def __init__(self, *args, **kwargs):
super(AForm, self).__init__(*args, **kwargs)
self.fields['photographer'].queryset = Photographer.objects.all()
(as actually recommended in Rob Osbourne's accepted answer to your other question).
Beyond that, I am suspicious of your fields_dict() method, which isn't part of Django and you haven't provided the definition for. Confirm with print self.fields.keys() that, for whatever mysterious reason, photographer is not there, then post the code for fields_dict().

Related

django-filter: Using ChoiceFilter with choices dependent on request

I am using django-filter and need to add a ChoiceFilter with choices dependent on the request that I receive. I am reading the docs for ChoiceFilter but it says: This filter matches values in its choices argument. The choices must be explicitly passed when the filter is declared on the FilterSet.
So is there any way to get request-dependent choices in the ChoiceFilter?
I haven't actually written the code but the following is what I want -
class F(FilterSet):
status = ChoiceFilter(choices=?) #choices depend on request
class Meta:
model = User
fields = ['status']
I've been looking too hard that I found two different ways of doing it! (both by overriding the __init__ method). Code inspired from this question.
class LayoutFilterView(filters.FilterSet):
supplier = filters.ChoiceFilter(
label=_('Supplier'), empty_label=_("All Suppliers"),)
def __init__(self, *args, **kwargs):
super(LayoutFilterView, self).__init__(*args, **kwargs)
# First Method
self.filters['supplier'].extra['choices'] = [
(supplier.id, supplier.id) for supplier in ourSuppliers(request=self.request)
]
# Second Method
self.filters['supplier'].extra.update({
'choices': [(supplier.id, supplier.name) for supplier in ourSuppliers(request=self.request)]
})
The function ourSuppliers is just to return a QuerySet to be used as choices
def ourSuppliers(request=None):
if request is None:
return Supplier.objects.none()
company = request.user.profile.company
return Supplier.objects.filter(company=company)

Django Rest Framework and Django-Hvad

so i needed to had some model-translation support for my DRF API and i started using django-hvad.
It seems to work well with my django application but i am getting some issues with the DRF APi.
I am trying to create a simple POST request and i am getting a error:
Accessing a translated field requires that the instance has a translation loaded, or a valid translation in current language (en) loadable from the database
Here are my models, serializers and viewsets:
Model:
class Mission(TranslatableModel):
translations = TranslatedFields(
mission=models.CharField(max_length=255, help_text="Mission name"),
)
def __unicode__(self):
return self.lazy_translation_getter('mission', str(self.pk))
Serializer:
class MissionSerializer(serializers.ModelSerializer):
mission = serializers.CharField(source='mission')
class Meta:
model = Mission
Viewset:
class MissionViewSet(viewsets.ModelViewSet):
queryset = Mission.objects.language().all()
serializer_class = MissionSerializer
authentication_classes = (NoAuthentication,)
permission_classes = (AllowAny,)
def get_queryset(self):
# Set Language For Translations
user_language = self.request.GET.get('language')
if user_language:
translation.activate(user_language)
return Mission.objects.language().all()
Does anyone know how i can get around this?? I am also opened to other suggested apps known to work but i would really like to have this one working
I got this to work thanks to the Spectras here https://github.com/KristianOellegaard/django-hvad/issues/211
The issue, I guess is DRF tries to do some introspection on the model. I do use DRF in a project of mine, on a TranslatableModel. It needs some glue to work properly. I once suggested adding that to hvad, but we concluded that that would be overextending the feature set. Maybe another module some day, but I don't have enough time to maintain both hvad and that.
It's been some time since I implemented it, so here it is as is:
# hvad compatibility for rest_framework - JHA
class TranslatableModelSerializerOptions(serializers.ModelSerializerOptions):
def __init__(self, meta):
super(TranslatableModelSerializerOptions, self).__init__(meta)
# We need this ugly hack as ModelSerializer hardcodes a read_only_fields check
self.translated_read_only_fields = getattr(meta, 'translated_read_only_fields', ())
self.translated_write_only_fields = getattr(meta, 'translated_write_only_fields', ())
class HyperlinkedTranslatableModelSerializerOptions(serializers.HyperlinkedModelSerializerOptions):
def __init__(self, meta):
super(HyperlinkedTranslatableModelSerializerOptions, self).__init__(meta)
# We need this ugly hack as ModelSerializer hardcodes a read_only_fields check
self.translated_read_only_fields = getattr(meta, 'translated_read_only_fields', ())
self.translated_write_only_fields = getattr(meta, 'translated_write_only_fields', ())
class TranslatableModelMixin(object):
def get_default_fields(self):
fields = super(TranslatableModelMixin, self).get_default_fields()
fields.update(self._get_translated_fields())
return fields
def _get_translated_fields(self):
ret = OrderedDict()
trans_model = self.opts.model._meta.translations_model
opts = trans_model._meta
forward_rels = [field for field in opts.fields
if field.serialize and not field.name in ('id', 'master')]
for trans_field in forward_rels:
if trans_field.rel:
raise RuntimeError()
field = self.get_field(trans_field)
if field:
ret[trans_field.name] = field
for field_name in self.opts.translated_read_only_fields:
assert field_name in ret
ret[field_name].read_only = True
for field_name in self.opts.translated_write_only_fields:
assert field_name in ret
ret[field_name].write_only = True
return ret
def restore_object(self, attrs, instance=None):
new_attrs = attrs.copy()
lang = attrs['language_code']
del new_attrs['language_code']
if instance is None:
# create an empty instance, pre-translated
instance = self.opts.model()
instance.translate(lang)
else:
# check we are updating the correct translation
tcache = self.opts.model._meta.translations_cache
translation = getattr(instance, tcache, None)
if not translation or translation.language_code != lang:
# nope, get the translation we are updating, or create it if needed
try:
translation = instance.translations.get_language(lang)
except instance.translations.model.DoesNotExist:
instance.translate(lang)
else:
setattr(instance, tcache, translation)
return super(TranslatableModelMixin, self).restore_object(new_attrs, instance)
class TranslatableModelSerializer(TranslatableModelMixin, serializers.ModelSerializer):
_options_class = TranslatableModelSerializerOptions
class HyperlinkedTranslatableModelSerializer(TranslatableModelMixin,
serializers.HyperlinkedModelSerializer):
_options_class = HyperlinkedTranslatableModelSerializerOptions
From there, you just inherit your serializers from TranslatableModelSerializer or HyperlinkedTranslatableModelSerializer. When POSTing, you should simple add language_code as a normal field as part of your JSON / XML / whatever.
The main trick is in the restore_object method. Object creation needs to include translation loading.

Creating a Django form from a dictionary of objects

I feel like this must be really simple, but after a couple of days of trying I'm officially clueless.
I have a dictionary where the keys are objects and the values are lists of objects. Here's how I want to use that info to construct a form:
for object in dictionary:
name_of_field = object.slug
name_of_field = forms.ModelMultipleChoiceField(widgets=forms.CheckboxSelectMultiple, queryset=dictionary[object])
Of course, just putting name_of_field in there twice doesn't work to generate dynamically named fields. What this actually does is create a single field called "name_of_field" using the final object it iterates over. I wish it would create a field for every key in the dictionary, named using the key object's slug and with a choice set of that key's values.
Is there a way to loop through this dictionary and create the form fields I want? I feel like the answer lies in superclassing __init__, but I still can't wrap my head around how to get multiple fields with different names.
You don't say where you are using this code. You should be putting it into the form's __init__ method, from where you can reference self.fields:
class DynamicForm(forms.Form):
def __init__(self, *args, **kwargs):
dynamic_fields = kwargs.pop('dynamic_fields')
super(DynamicForm, self).__init__(*args, **kwargs)
for key, value in dynamic_fields:
self.fields[key.slug] = forms.ModelMultipleChoiceField(widget=forms.CheckboxSelectMultiple, queryset=value)
I feel like I want to contribute to this question although it is really old, because I could not solve my question with the answer alone.
For a given model with the form:
class Product(models.Model):
data = models.JSONField()
store = models.ForeignKey(Store, on_delete = models.CASCADE)
number = models.PositiveIntegerField()
a dynamically created form can be created (careful, this is a simplified case, where all products of the same store share the same keys in the JSONField):
class ProductForm(forms.Form):
def __init__(self, first_product, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["number"] = forms.IntegerField(required = True)
for key in first_product.data.keys():
self.fields[key] = forms.CharField(required = False)
class Meta:
model = Product
fields = ["number", "data"]
The form must be called by handing it the first_product variable in the view:
class SomeView(TemplateView):
template_name = "appName/sometemplatename.html"
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(**kwargs)
store = Store.objects.get(user = self.request.user)
tmp = Product.objects.filter(store = store).first()
context["testform"] = ProductForm(first_product = tmp)
return context
This is a vastly general approach to start from.

Django creating a form field that's read only using widgets

My form field looks something like the following:
class FooForm(ModelForm):
somefield = models.CharField(
widget=forms.TextInput(attrs={'readonly':'readonly'})
)
class Meta:
model = Foo
Geting an error like the following with the code above: init() got an unexpected keyword argument 'widget'
I thought this is a legitimate use of a form widget?
You should use a form field and not a model field:
somefield = models.CharField(
widget=forms.TextInput(attrs={'readonly': 'readonly'})
)
replaced with
somefield = forms.CharField(
widget=forms.TextInput(attrs={'readonly': 'readonly'})
)
Should fix it.
Note that the readonly attribute does not keep Django from processing any value sent by the client. If it is important to you that the value doesn't change, no matter how creative your users are with FireBug, you need to use a more involved method, e.g. a ReadOnlyField/ReadOnlyWidget like demonstrated in a blog entry by Alex Gaynor.
I was going into the same problem so I created a Mixin that seems to work for my use cases.
class ReadOnlyFieldsMixin(object):
readonly_fields =()
def __init__(self, *args, **kwargs):
super(ReadOnlyFieldsMixin, self).__init__(*args, **kwargs)
for field in (field for name, field in self.fields.iteritems() if name in self.readonly_fields):
field.widget.attrs['disabled'] = 'true'
field.required = False
def clean(self):
cleaned_data = super(ReadOnlyFieldsMixin,self).clean()
for field in self.readonly_fields:
cleaned_data[field] = getattr(self.instance, field)
return cleaned_data
Usage, just define which ones must be read only:
class MyFormWithReadOnlyFields(ReadOnlyFieldsMixin, MyForm):
readonly_fields = ('field1', 'field2', 'fieldx')
As Benjamin (https://stackoverflow.com/a/2359167/565525) nicely explained, additionally to rendering correctly, you need to process field on backend properly.
There is an SO question and answers that has many good solutions. But anyway:
1) first approach - removing field in save() method, e.g. (not tested ;) ):
def save(self, *args, **kwargs):
for fname in self.readonly_fields:
if fname in self.cleaned_data:
del self.cleaned_data[fname]
return super(<form-name>, self).save(*args,**kwargs)
2) second approach - reset field to initial value in clean method:
def clean_<fieldname>(self):
return self.initial[<fieldname>] # or getattr(self.instance, <fieldname>)
Based on second approach I generalized it like this:
from functools import partial
class <Form-name>(...):
def __init__(self, ...):
...
super(<Form-name>, self).__init__(*args, **kwargs)
...
for i, (fname, field) in enumerate(self.fields.iteritems()):
if fname in self.readonly_fields:
field.widget.attrs['readonly'] = "readonly"
field.required = False
# set clean method to reset value back
clean_method_name = "clean_%s" % fname
assert clean_method_name not in dir(self)
setattr(self, clean_method_name, partial(self._clean_for_readonly_field, fname=fname))
def _clean_for_readonly_field(self, fname):
""" will reset value to initial - nothing will be changed
needs to be added dynamically - partial, see init_fields
"""
return self.initial[fname] # or getattr(self.instance, fname)

Actions triggered by field change in Django

How do I have actions occur when a field gets changed in one of my models? In this particular case, I have this model:
class Game(models.Model):
STATE_CHOICES = (
('S', 'Setup'),
('A', 'Active'),
('P', 'Paused'),
('F', 'Finished')
)
name = models.CharField(max_length=100)
owner = models.ForeignKey(User)
created = models.DateTimeField(auto_now_add=True)
started = models.DateTimeField(null=True)
state = models.CharField(max_length=1, choices=STATE_CHOICES, default='S')
and I would like to have Units created, and the 'started' field populated with the current datetime (among other things), when the state goes from Setup to Active.
I suspect that a model instance method is needed, but the docs don't seem to have much to say about using them in this manner.
Update: I've added the following to my Game class:
def __init__(self, *args, **kwargs):
super(Game, self).__init__(*args, **kwargs)
self.old_state = self.state
def save(self, force_insert=False, force_update=False):
if self.old_state == 'S' and self.state == 'A':
self.started = datetime.datetime.now()
super(Game, self).save(force_insert, force_update)
self.old_state = self.state
It has been answered, but here's an example of using signals, post_init and post_save.
from django.db.models.signals import post_save, post_init
class MyModel(models.Model):
state = models.IntegerField()
previous_state = None
#staticmethod
def post_save(sender, instance, created, **kwargs):
if instance.previous_state != instance.state or created:
do_something_with_state_change()
#staticmethod
def remember_state(sender, instance, **kwargs):
instance.previous_state = instance.state
post_save.connect(MyModel.post_save, sender=MyModel)
post_init.connect(MyModel.remember_state, sender=MyModel)
Basically, you need to override the save method, check if the state field was changed, set started if needed and then let the model base class finish persisting to the database.
The tricky part is figuring out if the field was changed. Check out the mixins and other solutions in this question to help you out with this:
Dirty fields in django
Django has a nifty feature called signals, which are effectively triggers that are set off at specific times:
Before/after a model's save method is called
Before/after a model's delete method is called
Before/after an HTTP request is made
Read the docs for full info, but all you need to do is create a receiver function and register it as a signal. This is usually done in models.py.
from django.core.signals import request_finished
def my_callback(sender, **kwargs):
print "Request finished!"
request_finished.connect(my_callback)
Simple, eh?
One way is to add a setter for the state. It's just a normal method, nothing special.
class Game(models.Model):
# ... other code
def set_state(self, newstate):
if self.state != newstate:
oldstate = self.state
self.state = newstate
if oldstate == 'S' and newstate == 'A':
self.started = datetime.now()
# create units, etc.
Update: If you want this to be triggered whenever a change is made to a model instance, you can (instead of set_state above) use a __setattr__ method in Game which is something like this:
def __setattr__(self, name, value):
if name != "state":
object.__setattr__(self, name, value)
else:
if self.state != value:
oldstate = self.state
object.__setattr__(self, name, value) # use base class setter
if oldstate == 'S' and value == 'A':
self.started = datetime.now()
# create units, etc.
Note that you wouldn't especially find this in the Django docs, as it (__setattr__) is a standard Python feature, documented here, and is not Django-specific.
note: Don't know about versions of django older than 1.2, but this code using __setattr__ won't work, it'll fail just after the second if, when trying to access self.state.
I tried something similar, and I tried to fix this problem by forcing the initialization of state (first in __init__ then ) in __new__ but this will lead to nasty unexpected behaviour.
I'm editing instead of commenting for obvious reasons, also: I'm not deleting this piece of code since maybe it could work with older (or future?) versions of django, and there may be another workaround to the self.state problem that i'm unaware of
#dcramer came up with a more elegant solution (in my opinion) for this issue.
https://gist.github.com/730765
from django.db.models.signals import post_init
def track_data(*fields):
"""
Tracks property changes on a model instance.
The changed list of properties is refreshed on model initialization
and save.
>>> #track_data('name')
>>> class Post(models.Model):
>>> name = models.CharField(...)
>>>
>>> #classmethod
>>> def post_save(cls, sender, instance, created, **kwargs):
>>> if instance.has_changed('name'):
>>> print "Hooray!"
"""
UNSAVED = dict()
def _store(self):
"Updates a local copy of attributes values"
if self.id:
self.__data = dict((f, getattr(self, f)) for f in fields)
else:
self.__data = UNSAVED
def inner(cls):
# contains a local copy of the previous values of attributes
cls.__data = {}
def has_changed(self, field):
"Returns ``True`` if ``field`` has changed since initialization."
if self.__data is UNSAVED:
return False
return self.__data.get(field) != getattr(self, field)
cls.has_changed = has_changed
def old_value(self, field):
"Returns the previous value of ``field``"
return self.__data.get(field)
cls.old_value = old_value
def whats_changed(self):
"Returns a list of changed attributes."
changed = {}
if self.__data is UNSAVED:
return changed
for k, v in self.__data.iteritems():
if v != getattr(self, k):
changed[k] = v
return changed
cls.whats_changed = whats_changed
# Ensure we are updating local attributes on model init
def _post_init(sender, instance, **kwargs):
_store(instance)
post_init.connect(_post_init, sender=cls, weak=False)
# Ensure we are updating local attributes on model save
def save(self, *args, **kwargs):
save._original(self, *args, **kwargs)
_store(self)
save._original = cls.save
cls.save = save
return cls
return inner
My solution is to put the following code to app's __init__.py:
from django.db.models import signals
from django.dispatch import receiver
#receiver(signals.pre_save)
def models_pre_save(sender, instance, **_):
if not sender.__module__.startswith('myproj.myapp.models'):
# ignore models of other apps
return
if instance.pk:
old = sender.objects.get(pk=instance.pk)
fields = sender._meta.local_fields
for field in fields:
try:
func = getattr(sender, field.name + '_changed', None) # class function or static function
if func and callable(func) and getattr(old, field.name, None) != getattr(instance, field.name, None):
# field has changed
func(old, instance)
except:
pass
and add <field_name>_changed static method to my model class:
class Product(models.Model):
sold = models.BooleanField(default=False, verbose_name=_('Product|sold'))
sold_dt = models.DateTimeField(null=True, blank=True, verbose_name=_('Product|sold datetime'))
#staticmethod
def sold_changed(old_obj, new_obj):
if new_obj.sold is True:
new_obj.sold_dt = timezone.now()
else:
new_obj.sold_dt = None
then the sold_dt field will change when sold field changes.
Any changes of any field defined in the model will trigger the <field_name>_changed method, with old and new object as parameters.
Using Dirty to detect changes and over-writing save method
dirty field
My prev ans: Actions triggered by field change in Django
class Game(DirtyFieldsMixin, models.Model):
STATE_CHOICES = (
('S', 'Setup'),
('A', 'Active'),
('P', 'Paused'),
('F', 'Finished')
)
state = models.CharField(max_length=1, choices=STATE_CHOICES, default='S')
def save(self, *args, **kwargs):
if self.is_dirty():
dirty_fields = self.get_dirty_fields()
if 'state' in dirty_fields:
Do_some_action()
super().save(*args, **kwargs)
If you use PostgreSQL you can create a trigger:
https://www.postgresql.org/docs/current/sql-createtrigger.html
Example:
CREATE TRIGGER check_update
BEFORE UPDATE ON accounts
FOR EACH ROW
WHEN (OLD.balance IS DISTINCT FROM NEW.balance)
EXECUTE FUNCTION check_account_update();

Categories