Sort field values alphabetically using marshmallow - python

I need to sort the values for each field alphabetically when they get returned by the API. It seems marshmallow's pre_dump method is the way to pre-process the data before serializing, but I haven't been able to figure this out. I've read the docs multiple times and Googled but haven't found the answer.
class UserSettingsSchema(UserSchema):
class Meta:
fields = (
"id",
"injuries",
"channel_levels",
"movement_levels",
"equipments",
"goals",
"genres"
)
#pre_dump
def pre_dump_hook(): # what do I pass in here?
# alphabetize field values, eg, all the genres will be sorted
equipments = fields.Nested(EquipmentSchema, many=True)
goals = fields.Nested(GoalSchemaBrief, many=True)
genres = fields.Nested(GenreSchemaBrief, many=True)

The answer given by PoP does not address the fact that I needed the values sorted. This is how I did it:
#post_load
def post_load_hook(self, item):
item['genres'] = sorted(item['genres'])
return item

As mentioned in the docs:
By default, receives a single object at a time, regardless of whether many=True is passed to the Schema.
Here's an example:
from marshmallow import *
class MySchema(Schema):
name = fields.String()
#pre_dump
def pre_dump_hook(self, instance):
instance['name'] = 'Hello %s' % instance['name']
Now you can do:
schema = MySchema()
schema.dump({'name': 'Bill'})
>>> MarshalResult(data={'name': 'Hello Bill'}, errors={})

Related

Can't sort table by model's property (accessor set)

I can't sort table by it's models property. I know that I should set accessor in the column so django-tables2 knows what field to process but it does not work.
This is the table:
class ScansTable(tables.Table):
site = tables.columns.Column(accessor='occurence.site', verbose_name='Site')
url = tables.columns.TemplateColumn("""{{ record.occurence.url|truncatechars:20 }}""",
accessor='occurence.url', verbose_name='Url')
price = tables.columns.TemplateColumn(u"""{{ record.price }} €""")
date = tables.columns.Column(accessor='date',order_by='date')
time = tables.columns.Column(accessor='time',order_by='time')
class Meta:
model = Scan
fields = ('date', 'time', 'site', 'url', 'valid', 'price')
attrs = {'id': 'cans_table',
'class': 'table',}
This is the Scan model:
class Scan(models.Model):
occurence = models.ForeignKey('Occurence', related_name='scans')
datetime = models.DateTimeField()
price = models.DecimalField(max_digits=20,decimal_places=2,null=True,blank=True,verbose_name='Price')
valid = models.BooleanField(default=True,verbose_name='Valid')
def __unicode__(self):
return u'{} {} {} {}'.format(self.occurence, self.datetime, self.price, u'OK' if self.valid else 'NOK')
#property
def date(self):
return self.datetime.date()
#property
def time(self):
return self.datetime.time()
The view:
def scans(request):
...
scans = Scan.objects.filter(occurence__product=product)
scans_table = ScansTable(scans)
RequestConfig(request).configure(scans_table)
scans_table.paginate(page=request.GET.get('page', 1), per_page=50)
return render(request,"dashboard_app/scans.html",context={'scans_table':scans_table})
The table is being properly renderd when I don't want to sort it. When I click on time (for example), it returns:
Cannot resolve keyword u'time' into field. Choices are: datetime,
groups, id, occurence, occurence_id, price, valid
Do you know where is the problem?
it's strange what the type product ?? you show the Occurence model and what value it in the view
It appears that defined properties/methods of the model are not available for sorting/filtering within the queryset. I don't fully understand why that is the case. A solution would be to NOT define date and time as properties on the Scan model, but instead annotate them to the queryset used to populate the data.
from django.db import models
def scans(request):
...
scans = Scan.objects.filter(occurence__product=product).annotate(
date=models.F('datetime__date'),
time=models.F('datetime__time')
)
...
See the documentation here on field lookups. Also you could use the tables specific columns for those fields - note that you don't need to define the accessors now the results are already in the queryset:
class ScansTable(tables.Table):
...
date = tables.DateColumn()
time = tables.TimeColumn()
...

use existing field as _id using elasticsearch dsl python DocType

I have class, where I try to set student_id as _id field in elasticsearch. I am referring persistent example from elasticsearch-dsl docs.
from elasticsearch_dsl import DocType, String
ELASTICSEARCH_INDEX = 'student_index'
class StudentDoc(DocType):
'''
Define mapping for Student type
'''
student_id = String(required=True)
name = String(null_value='')
class Meta:
# id = student_id
index = ELASTICSEARCH_INDEX
I tied by setting id in Meta but it not works.
I get solution as override save method and I achieve this
def save(self, **kwargs):
'''
Override to set metadata id
'''
self.meta.id = self.student_id
return super(StudentDoc, self).save(**kwargs)
I am creating this object as
>>> a = StudentDoc(student_id=1, tags=['test'])
>>> a.save()
Is there any direct way to set from Meta without override save method ?
There are a few ways to assign an id:
You can do it like this
a = StudentDoc(meta={'id':1}, student_id=1, tags=['test'])
a.save()
Like this:
a = StudentDoc(student_id=1, tags=['test'])
a.meta.id = 1
a.save()
Also note that before ES 1.5, one was able to specify a field to use as the document _id (in your case, it could have been student_id), but this has been deprecated in 1.5 and from then onwards you must explicitly provide an ID or let ES pick one for you.

Foreign Key Resource from dynamic field

I've got an API endpoint called TrackMinResource, which returns the minimal data for a music track, including the track's main artist returned as an ArtistMinResource. Here are the definitions for both:
class TrackMinResource(ModelResource):
artist = fields.ForeignKey(ArtistMinResource, 'artist', full=True)
class Meta:
queryset = Track.objects.all()
resource_name = 'track-min'
fields = ['id', 'artist', 'track_name', 'label', 'release_year', 'release_name']
include_resource_uri = False
cache = SimpleCache(public=True)
def dehydrate(self, bundle):
bundle.data['full_artist_name'] = bundle.obj.full_artist_name()
if bundle.obj.image_url != settings.NO_TRACK_IMAGE:
bundle.data['image_url'] = bundle.obj.image_url
class ArtistMinResource(ModelResource):
class Meta:
queryset = Artist.objects.all()
resource_name = 'artist-min'
fields = ['id', 'artist_name']
cache = SimpleCache(public=True)
def get_resource_uri(self, bundle_or_obj):
return '/api/v1/artist/' + str(bundle_or_obj.obj.id) + '/'
The problem is, the artist field on Track (previously a ForeignKey) is now a model method called main_artist (I've changed the structure of the database somewhat, but I'd like the API to return the same data as it did before). Because of this, I get this error:
{"error": "The model '<Track: TrackName>' has an empty attribute 'artist' and doesn't allow a null value."}
If I take out full=True from the 'artist' field of TrackMinResource and add null=True instead, I get null values for the artist field in the returned data. If I then assign the artist in dehydrate like this:
bundle.data['artist'] = bundle.obj.main_artist()
...I just get the artist name in the returned JSON, rather than a dict representing an ArtistMinResource (along with the associated resource_uris, which I need).
Any idea how to get these ArtistMinResources into my TrackMinResource? I can access an ArtistMinResource that comes out fine using the URL endpoint and asking for it by ID. Is there a function for getting that result from within the dehydrate function for TrackMinResource?
You can use your ArtistMinResource in TrackMinResource's dehydrate like this (assuming that main_artist() returns the object that your ArtistMinResource represents):
artist_resource = ArtistMinResource()
artist_bundle = artist_resource.build_bundle(obj=bundle.obj.main_artist(), request=request)
artist_bundle = artist_resource.full_dehydrate(artist_bundle)
artist_json = artist_resource.serialize(request=request, data=artist_bundle, format='application/json')
artist_json should now contain your full artist representation. Also, I'm pretty sure you don't have to pass the format if you pass the request and it has a content-type header populated.

How do I generate dynamic fields in WTForms

I am trying to generate a form in WTForms that has dynamic fields according to this documentation http://wtforms.simplecodes.com/docs/1.0.2/specific_problems.html#dynamic-form-composition
I have this subform class which allows users to pick items to purchase from a list:
class Item(Form):
itmid = SelectField('Item ID')
qty = IntegerField('Quantity')
class F(Form):
pass
There will be more than one category of shopping items, so I would like to generate a dynamic select field based on what categories the user will choose:
fld = FieldList(FormField(Item))
fld.append_entry()
but I get the following error:
AttributeError: 'UnboundField' object has no attribute 'append_entry'
Am I doing something wrong, or is there no way to accomplish this in WTForms?
I ran into this issue tonight and ended up with this. I hope this helps future people.
RecipeForm.py
class RecipeForm(Form):
category = SelectField('Category', choices=[], coerce=int)
...
views.py
#mod.route('/recipes/create', methods=['POST'])
def validateRecipe():
categories = [(c.id, c.name) for c in g.user.categories.order_by(Category.name).all()]
form = RecipeForm(request.form)
form.category.choices = categories
...
#mod.route('/recipes/create', methods=['GET'])
def createRecipe():
categories = [(c.id, c.name) for c in g.user.categories.order_by(Category.name).all()]
form = RecipeForm(request.form)
form.category.choices = categories
return render_template('recipes/createRecipe.html', form=form)
I found this post helpful as well
class BaseForm(Form):
#classmethod
def append_field(cls, name, field):
setattr(cls, name, field)
return cls
from forms import TestForm
form = TestForm.append_field("do_you_want_fries_with_that",BooleanField('fries'))(obj=db_populate_object)
I use the extended class BaseForm for all my forms and have a convenient append_field function on class.
Returns the class with the field appended, since instances (of Form fields) can't append fields.
Posting without writing full code or testing the code, but maybe it will give you some ideas. Also this could maybe only help with the filling the needed data.
You need to fill choices for SelectField to be able to see the data and be able to select it. Where you fill that? Initial fill should be in the form definition, but if you like dynamic one, I would suggest to modify it in the place where you creating this form for showing to the user. Like the view where you do some form = YourForm() and then passing it to the template.
How to fill form's select field with choices? You must have list of tuples and then something like this:
form.category_select.choices = [(key, categories[key]) for key in categories]
form.category_select.choices.insert(0, ("", "Some default value..."))
categories here must be dictionary containing your categories in format like {1:'One', 2:'Two',...}
So if you will assign something to choices when defining the form it will have that data from the beginning, and where you need to have user's categories, just overwrite it in the view.
Hope that will give you some ideas and you can move forward :)
have you tried calling append_entry() on the form instance instead of the FieldList definition?
class F(Form)
fld = FieldList(SelectField(Item))
form = F()
form.fld.append_entry()
This is how i got it to work.
class MyForm(FlaskForm):
mylist = SelectField('Select Field', choices=[])
#app.route("/test", methods=['GET', 'POST']
def testview():
form = MyForm()
form.mylist.choices = [(str(i), i) for i in range(9)]
Strangely this whole thing stops working for me if i use coerce=int. I am myself a flask beginner, so i am not really sure why coerce=int causes issue.
WTForms Documentation : class wtforms.fields.SelectField
Select fields with dynamic choice values:
class UserDetails(Form):
group_id = SelectField(u'Group', coerce=int)
def edit_user(request, id):
user = User.query.get(id)
form = UserDetails(request.POST, obj=user)
form.group_id.choices = [(g.id, g.name) for g in Group.query.order_by('name')]

How would you inherit from and override the django model classes to create a listOfStringsField?

I want to create a new type of field for django models that is basically a ListOfStrings. So in your model code you would have the following:
models.py:
from django.db import models
class ListOfStringsField(???):
???
class myDjangoModelClass():
myName = models.CharField(max_length=64)
myFriends = ListOfStringsField() #
other.py:
myclass = myDjangoModelClass()
myclass.myName = "bob"
myclass.myFriends = ["me", "myself", "and I"]
myclass.save()
id = myclass.id
loadedmyclass = myDjangoModelClass.objects.filter(id__exact=id)
myFriendsList = loadedclass.myFriends
# myFriendsList is a list and should equal ["me", "myself", "and I"]
How would you go about writing this field type, with the following stipulations?
We don't want to do create a field which just crams all the strings together and separates them with a token in one field like this. It is a good solution in some cases, but we want to keep the string data normalized so tools other than django can query the data.
The field should automatically create any secondary tables needed to store the string data.
The secondary table should ideally have only one copy of each unique string. This is optional, but would be nice to have.
Looking in the Django code it looks like I would want to do something similar to what ForeignKey is doing, but the documentation is sparse.
This leads to the following questions:
Can this be done?
Has it been done (and if so where)?
Is there any documentation on Django about how to extend and override their model classes, specifically their relationship classes? I have not seen a lot of documentation on that aspect of their code, but there is this.
This is comes from this question.
There's some very good documentation on creating custom fields here.
However, I think you're overthinking this. It sounds like you actually just want a standard foreign key, but with the additional ability to retrieve all the elements as a single list. So the easiest thing would be to just use a ForeignKey, and define a get_myfield_as_list method on the model:
class Friends(model.Model):
name = models.CharField(max_length=100)
my_items = models.ForeignKey(MyModel)
class MyModel(models.Model):
...
def get_my_friends_as_list(self):
return ', '.join(self.friends_set.values_list('name', flat=True))
Now calling get_my_friends_as_list() on an instance of MyModel will return you a list of strings, as required.
What you have described sounds to me really similar to the tags.
So, why not using django tagging?
It works like a charm, you can install it independently from your application and its API is quite easy to use.
I also think you're going about this the wrong way. Trying to make a Django field create an ancillary database table is almost certainly the wrong approach. It would be very difficult to do, and would likely confuse third party developers if you are trying to make your solution generally useful.
If you're trying to store a denormalized blob of data in a single column, I'd take an approach similar to the one you linked to, serializing the Python data structure and storing it in a TextField. If you want tools other than Django to be able to operate on the data then you can serialize to JSON (or some other format that has wide language support):
from django.db import models
from django.utils import simplejson
class JSONDataField(models.TextField):
__metaclass__ = models.SubfieldBase
def to_python(self, value):
if value is None:
return None
if not isinstance(value, basestring):
return value
return simplejson.loads(value)
def get_db_prep_save(self, value):
if value is None:
return None
return simplejson.dumps(value)
If you just want a django Manager-like descriptor that lets you operate on a list of strings associated with a model then you can manually create a join table and use a descriptor to manage the relationship. It's not exactly what you need, but this code should get you started.
Thanks for all those that answered. Even if I didn't use your answer directly the examples and links got me going in the right direction.
I am not sure if this is production ready, but it appears to be working in all my tests so far.
class ListValueDescriptor(object):
def __init__(self, lvd_parent, lvd_model_name, lvd_value_type, lvd_unique, **kwargs):
"""
This descriptor object acts like a django field, but it will accept
a list of values, instead a single value.
For example:
# define our model
class Person(models.Model):
name = models.CharField(max_length=120)
friends = ListValueDescriptor("Person", "Friend", "CharField", True, max_length=120)
# Later in the code we can do this
p = Person("John")
p.save() # we have to have an id
p.friends = ["Jerry", "Jimmy", "Jamail"]
...
p = Person.objects.get(name="John")
friends = p.friends
# and now friends is a list.
lvd_parent - The name of our parent class
lvd_model_name - The name of our new model
lvd_value_type - The value type of the value in our new model
This has to be the name of one of the valid django
model field types such as 'CharField', 'FloatField',
or a valid custom field name.
lvd_unique - Set this to true if you want the values in the list to
be unique in the table they are stored in. For
example if you are storing a list of strings and
the strings are always "foo", "bar", and "baz", your
data table would only have those three strings listed in
it in the database.
kwargs - These are passed to the value field.
"""
self.related_set_name = lvd_model_name.lower() + "_set"
self.model_name = lvd_model_name
self.parent = lvd_parent
self.unique = lvd_unique
# only set this to true if they have not already set it.
# this helps speed up the searchs when unique is true.
kwargs['db_index'] = kwargs.get('db_index', True)
filter = ["lvd_parent", "lvd_model_name", "lvd_value_type", "lvd_unique"]
evalStr = """class %s (models.Model):\n""" % (self.model_name)
evalStr += """ value = models.%s(""" % (lvd_value_type)
evalStr += self._params_from_kwargs(filter, **kwargs)
evalStr += ")\n"
if self.unique:
evalStr += """ parent = models.ManyToManyField('%s')\n""" % (self.parent)
else:
evalStr += """ parent = models.ForeignKey('%s')\n""" % (self.parent)
evalStr += "\n"
evalStr += """self.innerClass = %s\n""" % (self.model_name)
print evalStr
exec (evalStr) # build the inner class
def __get__(self, instance, owner):
value_set = instance.__getattribute__(self.related_set_name)
l = []
for x in value_set.all():
l.append(x.value)
return l
def __set__(self, instance, values):
value_set = instance.__getattribute__(self.related_set_name)
for x in values:
value_set.add(self._get_or_create_value(x))
def __delete__(self, instance):
pass # I should probably try and do something here.
def _get_or_create_value(self, x):
if self.unique:
# Try and find an existing value
try:
return self.innerClass.objects.get(value=x)
except django.core.exceptions.ObjectDoesNotExist:
pass
v = self.innerClass(value=x)
v.save() # we have to save to create the id.
return v
def _params_from_kwargs(self, filter, **kwargs):
"""Given a dictionary of arguments, build a string which
represents it as a parameter list, and filter out any
keywords in filter."""
params = ""
for key in kwargs:
if key not in filter:
value = kwargs[key]
params += "%s=%s, " % (key, value.__repr__())
return params[:-2] # chop off the last ', '
class Person(models.Model):
name = models.CharField(max_length=120)
friends = ListValueDescriptor("Person", "Friend", "CharField", True, max_length=120)
Ultimately I think this would still be better if it were pushed deeper into the django code and worked more like the ManyToManyField or the ForeignKey.
I think what you want is a custom model field.

Categories

Resources