When are Django model objects initialized? - python

I'm facing an interesting problem in Python with Django.
I think just by putting my code you will get what I'm trying to do.
views.py:
def ingredients(request):
objects = Ingredient.objects.all()[:50]
return render(request, 'template.html', {'objects': objects}
models.py:
class Ingredient(models.Model):
stock_by = models.IntegerField(null=False)
unit = ""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
unit_type = {
1: 'Units',
2: 'Kilograms',
3: 'Litters'
}
self.unit = unit_type[IntegerField.to_python(self.cost_by)]
Error:
TypeError at /Ingredient/
to_python() missing 1 required positional argument: 'value'
(Value is None).
init.py (Django framework class):
class IntegerField(field):
def to_python(self, value):
if value is None:
return value
try:
return int(value)
except (TypeError, ValueError):
raise exceptions.ValidationError(
self.error_messages['invalid'],
code='invalid',
params={'value': value},
)
I think it's clear what I'm trying to achieve. Just a String attribute which will take the name of the unit value (represented by integers in db).

to_python is an instance method you have to call it from the instance, not the class.
IntegerField().to_python(self.cost_by)

Related

WTForms: passing another container to validator

Having already looked at this answer, I'm having trouble getting my custom WTForm validator to accept another arg.
How my code is structures is that I have a form_create() method that is called via the following:
form = other_file.form_create(
form='page_settings',
data=data,
all_applications=all_applications,
)
all_applications being a dict I need to index through for one of my validators. form_create looks like the following:
def get_form(form=None, *args, **kwargs):
class Form(wtf.Form):
if form == 'page_settings':
my_host_name = HostName('Hostname', validators=[wtf.validators.DataRequired(), validate_hostname, validate_config])
I want validate_config to use data from the field and from all_applications, but even after putting it in HostName's init method as such:
class HostName(wtf.StringField):
def __init__(self, *args, **kwargs):
kwargs.setdefault('validators', [
wtf.validators.DataRequired(),
])
all_applications = kwargs.setdefault('all_applications')
super(HostNameField, self).__init__(*args, **kwargs)
But I keep getting errors like ‘Form’ object has no attribute ‘all_applications’
The custom validator in question looks like this:
def validate_config(form, field, all_applications):
if (some logic here is true):
raise wtf.ValidationError(
'some error string here'
)
Any ideas as to how I can get a custom validator to accept another container like all_applications?
You can set all_applications as an attribute of the field when the form is being initialised, then access it via the field in the validator.
def validate_config(form, field):
if field.data and field.all_applications is not None:
if field.data not in field.all_applications:
raise ValidationError('Invalid config')
class F(Form):
foo = StringField(validators=[validate_config])
def __init__(self, *args, **kwargs):
all_applications = kwargs.pop('all_applications', None)
super().__init__(*args, **kwargs)
# Make all_applications an attribute of field foo
self.foo.all_applications = all_applications
>>> from webob.multidict import MultiDict
>>> f = F(formdata=MultiDict(foo='a'), all_applications={'a': 1})
>>> f.validate()
True
>>> f = F(formdata=MultiDict(foo='b'), all_applications={'a': 1})
>>> f.validate()
False
>>> f.errors
{'foo': ['Invalid config']}

Marshmallow: How can I override this python class's constructor?

Below is my python marshmallow serializer which I'm using in my SQLAlchemy-based application.
import simplejson
from marshmallow import Schema
class MySerializer(Schema):
class Meta:
json_module = simplejson
fields = ('field1', 'field2', 'field3')
field3 = fields.Method('get_field3')
def get_field3(self, this_obj):
return "Hello"
Here is how I call this serializer:
my_argument = 1
items=MySerializer(documents=list_of_my_objects, many=True).data
Now, I want to change the serializer such that it takes an additional argument (I will pass in my_argument) and if the value of that argument is 1, return "Goodbye" in place of "Hello". How can I do that?
In particular, I don't know how I can pass in an argument to this marshamallow Schema such that it is available from insideget_field3. I know I need to override the __init__() method. But I'm not sure what it should look like. I tried the following, but it didn't work:
def __init__(self, documents, many, my_arg):
self.my_arg = my_arg
super(Schema, self).__init__(documents, many=many)
def get_field3(self, this_obj):
self.my_arg == 1:
return "Goodbye"
else:
return "Hello"
This was the stack-trace I got:
my_project/my_models/serializers.pyc in __init__(self, documents, many, my_arg)
---> 25 super(Schema, self).__init__(documents, many=many)
my_virtualenv/lib/python2.7/site-packages/marshmallow/schema.pyc in __init__(self, obj, extra, only, exclude, prefix, strict, many, skip_missing, context)
--> 273 self._update_fields(self.obj, many=many)
my_virtualenv/lib/python2.7/site-packages/marshmallow/schema.pyc in _update_fields(self, obj, many)
--> 636 ret = self.__filter_fields(field_names, obj, many=many)
my_virtualenv/lib/python2.7/site-packages/marshmallow/schema.pyc in __filter_fields(self, field_names, obj, many)
--> 683 attribute_type = type(obj_dict[key])
TypeError: list indices must be integers, not str
Just pop added arg from kwargs to avoid any errors (you can pass any kwargs to method redefined this way):
class MySerializer(Schema):
# ...
def __init__(self, *args, **kwargs):
self.my_arg = kwargs.pop('my_arg') if 'my_arg' in kwargs else None
super(Schema, self).__init__(*args, **kwargs)
now you can pass custom kwarg (keyword argument, not positional) to create objects:
MySerializer(documents=list_of_my_objects, many=True, my_arg=1)

How to override a Model Form widget in Django?

I'm trying to override render_option method present inside Select Widget class from my forms.py file. So I have added the method with the same name inside the corresponding Model form class. But it won't work (this method fails to override). My forms.py file looks like,
class CustomSelectMultiple(Select):
allow_multiple_selected = True
def render_option(self, selected_choices, option_value, option_label):
print 'Inside custom render_option\n\n'
if option_value is None:
option_value = ''
option_value = force_text(option_value)
if option_value in selected_choices:
selected_html = mark_safe(' selected="selected"')
if not self.allow_multiple_selected:
# Only allow for a single selection.
selected_choices.remove(option_value)
else:
selected_html = ''
return format_html('<option value="{}" data-img-src="www.foo.com" {}>{}</option>',
option_value,
selected_html,
force_text(option_label))
def render_options(self, choices, selected_choices):
print 'Inside custom render_options\n\n'
print self
print choices
# Normalize to strings.
selected_choices = set(force_text(v) for v in selected_choices)
output = []
for option_value, option_label in chain(self.choices, choices):
if isinstance(option_label, (list, tuple)):
output.append(format_html('<optgroup label="{}">', force_text(option_value)))
for option in option_label:
output.append(self.render_option(selected_choices, *option))
output.append('</optgroup>')
else:
output.append(self.render_option(selected_choices, option_value, option_label))
#print output
return '\n'.join(output)
def render(self, name, value, attrs=None, choices=()):
print 'Inside custom render\n\n'
if value is None:
value = []
final_attrs = self.build_attrs(attrs, name=name)
output = [format_html('<select multiple="multiple"{}>', flatatt(final_attrs))]
options = self.render_options(choices, value)
if options:
output.append(options)
output.append('</select>')
return mark_safe('\n'.join(output))
def value_from_datadict(self, data, files, name):
if isinstance(data, MultiValueDict):
return data.getlist(name)
return data.get(name)
class GuideUpdateForm(ModelForm):
def __init__(self, *args, **kwargs):
super(GuideUpdateForm, self).__init__(*args, **kwargs)
self.fields['date_modified'].widget = HiddenInput()
self.fields['point_of_interest'].widget = CustomSelectMultiple()
class Meta:
fields = ('name', 'image', 'point_of_interest', 'date_modified', )
model = Guide
I also tried changing my Meta class like,
class Meta:
fields = ('name', 'image', 'point_of_interest', 'date_modified', )
model = Guide
widgets = {
'point_of_interest': SelectMultiple(attrs={'data-img-src': 'www.foo.com'}),
}
But it add's the attribute data-img-src only to the select tag but not to all the option tags present inside the select tag.
Note that SelectMultiple class invokes the renderoptions method of Select class which further invokes the renderoption method which don't have attrs=None keyword argument.
Judging off your own solution it looks like you may have been looking for a ModelChoiceField
self.fields['point_of_interest'] = forms.ModelChoiceField(widget=CustomSelectMultiple(),
queryset=poi.objects.all())
The queryset parameter consists of "A QuerySet of model objects from which the choices for the field will be derived, and which will be used to validate the user’s selection."
does it create a list of tuples of ids, names? Because I want the option tag to look like option value="id">name</option>
I'm pretty sure the default is id, __str__ where __str__ is the string representation of the model. If you wanted this to be specific to the name then you could override this field and set label_from_instance
class MyModelChoiceField(ModelChoiceField):
def label_from_instance(self, obj):
return obj.name
I managed to solve this problem by passing db values to choices kwargs.
from models import poi
class GuideUpdateForm(ModelForm):
def __init__(self, *args, **kwargs):
super(GuideUpdateForm, self).__init__(*args, **kwargs)
self.fields['date_modified'].widget = HiddenInput()
self.fields['point_of_interest'] = forms.ChoiceField(widget=CustomSelectMultiple(), choices=[(i.id,i.name) for i in poi.objects.all()])

Convert list into queryset

To improve performance, In my project most of the model instances are stored in cache as list values. But all generic views in Django Rest Framework expect them to be queryset objects. How can I convert the values I got from list into a queryset like objeccts, such that I can use generic views.
Say, I have a function like
def cache_user_articles(user_id):
key = "articles_{0}".format(user_id)
articles = cache.get(key)
if articles is None:
articles = list(Article.objects.filter(user_id = user_id))
cache.set(key, articles)
return articles
In my views.py,
class ArticleViewSet(viewsets.ModelViewSet):
...
def get_queryset(self, request, *args, **kwargs):
return cache_user_articles(kwargs.get(user_id))
But, this of course this wouldn't work as Django Rest Framework expects the result of get_queryset to be QuerySet object, and on PUT request it would call 'get' method on it. Is there any way, I could make it to work with generic DRF views.
That's the place where Python like dynamic languages really shine due to Duck Typing. You could easily write something that quacks like a QuerySet.
import mongoengine
from bson import ObjectId
class DuckTypedQuerySet(list):
def __init__(self, data, document):
if not hasattr(data, '__iter__') or isinstance(data, mongoengine.Document):
raise TypeError("DuckTypedQuerySet requires iterable data")
super(DuckTypedQuerySet, self).__init__(data)
self._document = document
#property
def objects(self):
return self
def _query_match(self, instance, **kwargs):
is_match = True
for key, value in kwargs.items():
attribute = getattr(instance, key, None)
if isinstance(attribute, ObjectId) and not isinstance(value, ObjectId):
attribute = str(attribute)
if not attribute == value:
is_match = False
break
return is_match
def filter(self, **kwargs):
data = filter(lambda instance: self._query_match(instance, **kwargs), self)
return self.__class__(data, self._document)
def get(self, **kwargs):
results = self.filter(**kwargs)
if len(results) > 1:
raise self._document.MultipleObjectsReturned("{0} items returned, instead of 1".format(len(results)))
if len(results) < 1:
raise self._document.DoesNotExist("{0} matching query does not exist.".format(str(self._document)))
return results[0]
def first(self):
return next(iter(self), None)
def all(self):
return self
def count(self):
return len(self)
def cache_user_articles(user_id):
key = "articles_{0}".format(user_id)
articles = cache.get(key)
if articles is None:
articles = DuckTypedQuerySet(list(Article.objects.filter(user_id = user_id)), document = Article)
cache.set(key, articles)
return articles
Ofcourse, this is not an exhaustive implementation. You might need to add other methods that exists in queryset. But I think these will do for simple use-cases. Now you can make do with generic implementations of Django Rest Framework.
What about this?
(I have mocked the redis cache with a class variable)
class CachedManager(models.Manager):
cache = dict()
def cached(self, user_id):
cached = self.cache.get(user_id, [])
if not cached:
self.cache[user_id] = [article.pk for article in self.filter(user_id=user_id)]
return self.cache[user_id]
class Article(models.Model):
objects = CachedManager()
user_id = models.IntegerField()
# Whatever fields your Article model has
Then in your views or wherever you need it:
you can call Article.objects.cached(<a user id>)

Serialize the #property methods in a Python class

Is there a way to have any #property definitions passed through to a json serializer when serializing a Django model class?
example:
class FooBar(object.Model)
name = models.CharField(...)
#property
def foo(self):
return "My name is %s" %self.name
Want to serialize to:
[{
'name' : 'Test User',
'foo' : 'My name is Test User',
},]
You can extend Django's serializers without /too/ much work. Here's a custom serializer that takes a queryset and a list of attributes (fields or not), and returns JSON.
from StringIO import StringIO
from django.core.serializers.json import Serializer
class MySerializer(Serializer):
def serialize(self, queryset, list_of_attributes, **options):
self.options = options
self.stream = options.get("stream", StringIO())
self.start_serialization()
for obj in queryset:
self.start_object(obj)
for field in list_of_attributes:
self.handle_field(obj, field)
self.end_object(obj)
self.end_serialization()
return self.getvalue()
def handle_field(self, obj, field):
self._current[field] = getattr(obj, field)
Usage:
>>> MySerializer().serialize(MyModel.objects.all(), ["field1", "property2", ...])
Of course, this is probably more work than just writing your own simpler JSON serializer, but maybe not more work than your own XML serializer (you'd have to redefine "handle_field" to match the XML case in addition to changing the base class to do that).
The solution worked well that is proposed by M. Rafay Aleem and Wtower, but it's duplicated lot of code. Here is an improvment:
from django.core.serializers.base import Serializer as BaseSerializer
from django.core.serializers.python import Serializer as PythonSerializer
from django.core.serializers.json import Serializer as JsonSerializer
class ExtBaseSerializer(BaseSerializer):
def serialize_property(self, obj):
model = type(obj)
for field in self.selected_fields:
if hasattr(model, field) and type(getattr(model, field)) == property:
self.handle_prop(obj, field)
def handle_prop(self, obj, field):
self._current[field] = getattr(obj, field)
def end_object(self, obj):
self.serialize_property(obj)
super(ExtBaseSerializer, self).end_object(obj)
class ExtPythonSerializer(ExtBaseSerializer, PythonSerializer):
pass
class ExtJsonSerializer(ExtPythonSerializer, JsonSerializer):
pass
How to use it:
ExtJsonSerializer().serialize(MyModel.objects.all(), fields=['field_name_1', 'property_1' ...])
This is a combination of M. Rafay Aleem and Wtowers answer and caots.
This is DRY and lets you only specify the extra props instead of all fields and props as in caots version.
from django.core.serializers.json import Serializer as JsonSerializer
from django.core.serializers.python import Serializer as PythonSerializer
from django.core.serializers.base import Serializer as BaseSerializer
class ExtBaseSerializer(BaseSerializer):
def serialize(self, queryset, **options):
self.selected_props = options.pop('props')
return super(ExtBaseSerializer, self).serialize(queryset, **options)
def serialize_property(self, obj):
model = type(obj)
for field in self.selected_props:
if hasattr(model, field) and type(getattr(model, field)) == property:
self.handle_prop(obj, field)
def handle_prop(self, obj, field):
self._current[field] = getattr(obj, field)
def end_object(self, obj):
self.serialize_property(obj)
super(ExtBaseSerializer, self).end_object(obj)
class ExtPythonSerializer(ExtBaseSerializer, PythonSerializer):
pass
class ExtJsonSerializer(ExtPythonSerializer, JsonSerializer):
pass
How to use it:
ExtJsonSerializer().serialize(MyModel.objects.all(), props=['property_1', ...])
Things have changed a bit since 2010, so the answer of #user85461 seems to no longer be working with Django 1.8 and Python 3.4. This is an updated answer with what seems to work for me.
from django.core.serializers.base import Serializer as BaseSerializer
from django.core.serializers.python import Serializer as PythonSerializer
from django.core.serializers.json import Serializer as JsonSerializer
from django.utils import six
class ExtBaseSerializer(BaseSerializer):
""" Abstract serializer class; everything is the same as Django's base except from the marked lines """
def serialize(self, queryset, **options):
self.options = options
self.stream = options.pop('stream', six.StringIO())
self.selected_fields = options.pop('fields', None)
self.selected_props = options.pop('props', None) # added this
self.use_natural_keys = options.pop('use_natural_keys', False)
self.use_natural_foreign_keys = options.pop('use_natural_foreign_keys', False)
self.use_natural_primary_keys = options.pop('use_natural_primary_keys', False)
self.start_serialization()
self.first = True
for obj in queryset:
self.start_object(obj)
concrete_model = obj._meta.concrete_model
for field in concrete_model._meta.local_fields:
if field.serialize:
if field.rel is None:
if self.selected_fields is None or field.attname in self.selected_fields:
self.handle_field(obj, field)
else:
if self.selected_fields is None or field.attname[:-3] in self.selected_fields:
self.handle_fk_field(obj, field)
for field in concrete_model._meta.many_to_many:
if field.serialize:
if self.selected_fields is None or field.attname in self.selected_fields:
self.handle_m2m_field(obj, field)
# added this loop
if self.selected_props:
for field in self.selected_props:
self.handle_prop(obj, field)
self.end_object(obj)
if self.first:
self.first = False
self.end_serialization()
return self.getvalue()
# added this function
def handle_prop(self, obj, field):
self._current[field] = getattr(obj, field)
class ExtPythonSerializer(ExtBaseSerializer, PythonSerializer):
pass
class ExtJsonSerializer(ExtPythonSerializer, JsonSerializer):
pass
Usage:
>>> ExtJsonSerializer().serialize(MyModel.objects.all(), fields=['myfield', ...], props=['myprop', ...])
You can get all of the properties of a class using some black magic:
def list_class_properties(cls):
return [k for k,v in cls.__dict__.iteritems() if type(v) is property]
For example:
>>> class Foo:
#property
def bar(self):
return "bar"
>>> list_class_properties(Foo)
['bar']
Then you can build the dictionary and serialize it from there.

Categories

Resources