Serializing attributes that are not model fields with Django JSON serializer - python

Not sure what the difference is but I have two snippets of code that i assume should behave the same.
This works:
channels = RpChannels.objects.using('prod').all().filter(userid=id).order_by('-when')
for channel in channels:
date = channel.t.replace(",","-") if channel.t else "Default"
name = Playlists.objects.using('prod').get(id=channel.p).description if channel.p else "Default"
genres = ', '.join(StTag.objects.values_list('tag', flat = True).filter(id__in=channel.g.split(',')).order_by('tag')) if channel.g else "Default"
when = channel.when if channel.when else "N/A"
setattr(channel, 'channel', name)
setattr(channel, 'genres', genres)
setattr(channel, 'date', date)
setattr(channel, 'when', when)
setattr(channel, 'valence', channel.v if channel.v else "Default")
setattr(channel, 'arousal', channel.a if channel.a else "Default")
context = {'st_user': user,
'devices': devices,
'feedback': feedback,
'stations': stations,
'channels': channels}
return render(request, 'user.html', context)
This doesn't:
tracks = Songplays.objects.using('prod').all().filter(user=id, when__gt=start, when__lt=end).order_by('-when')
for item in tracks:
track = Track.objects.get(id=item.trackid)
artist = Artist.objects.get(id=track.artist_id).name
title = TrackTitle.objects.get(id=track.id).title
setattr(item, 'duration', str(datetime.timedelta(seconds=track.duration)) if track.duration else "N/A")
setattr(item, 'title', title)
setattr(item, 'artist', artist)
data = serializers.serialize('json', tracks)
return HttpResponse(data, mimetype="application/json")
What i mean by doesn't work is, for example in the first one none of the setattr values exist but they get added to each individual channel and i can access them in my template. However, in the second strip of code duration exists so that modified value gets outputted in my JSON but the other 2 attributes, title and artist, don't exist in the original queryset but unlike with channels they don't get added. Could i have anything to do with the way it's being rendered? If so, why?

Since serializer only uses models field i decided to return a ValuesQuerySet instead of a QuerySet since the former returns a dict. I did this by getting values() instead of all(). Thanks to #PeterDeGlopper for pointing me in the right direction!
# this
tracks = Songplays.objects.using('prod').values().filter(user=id, when__gt=start, when__lt=end).order_by('-when')
#instead of this
tracks = Songplays.objects.using('prod').all().filter(user=id, when__gt=start, when__lt=end).order_by('-when')
Once I had the dict i was able to convert to JSON like this.
import json
data = json.dumps(list(tracks))

Related

Django modify objects using key=value options

I'm doing http POST processing of incoming data modifications requests (from a DataTables table) for objects in my Django environment. The POST data comes to me in key=value pairs.
The problem I'm running into is that I haven't found a way to turn this into something I can use. Django objects and modifications are done like this:
id = 123
item = Stuff.objects.get(id=id)
item.title = "New Title"
item.save()
I have the following data attributes:
id = 123
attribute = "title"
value = "New Title"
How can I use them to modify a Django object?
A more efficient way to do this is with:
id = 123
attribute = 'title'
value = 'New Title'
Stuff.objects.filter(id=id).update(**{attribute: value})
This will prevent first fetching the object with a query, and then update it.
If you need to load the object anyway, you can work with setattr(…) [Python-doc]:
id = 123
attribute = 'title'
value = 'New Title'
item = Stuff.objects.get(id=id)
setattr(item, attribute, value)
item.save()
Try using an if statement like
If attribute = 'title'
item.title = value
item.save()

What is the proper way to verify if a filter was used by the user to be added to filters in the backend Django?

I am asking for a suggestion abount how to detect which filters are being used by the user, a filtering system can have different options to get the data, but using if statements to check if a value comes in a POST and then add it to a filters set is not really a good option specially when there a lot of them.
# Some if statements detecting if a filter is used (if it is not null in the POST)
# Adding the filter to filters
filters = {
# filters after being added
'{}__{}'.format('categories', 'exact'): request.POST['category'],
'{}__{}'.format('price', 'gte'): request.POST['price'], # Only an example
}
products = Product.objects.filter(**filters)
This works, but i just want to know what would you recommend.
If I understood your question correctly, I would chain filters instead:
queryset = Product.objects.all()
if 'category' in request.POST:
queryset.filter(categories__exact=request.POST['category'])
if 'price' in request.POST:
queryset.filter(price__gte=request.POST['price'])
To expand on Gasanov's suggestion:
possible_filters = {
'category': 'exact',
'price': 'gte',
# etc. Not sure if this can be done any smarter
# maybe if you pass `cateogry__exact` in the POST data instead of just `category`?
}
queryset = Product.objects.all()
for key, val in request.POST.items():
if key in possible_filters:
filter_kwargs = {
f'{key}__{possible_filters[key]}': val,
}
queryset = queryset.filter(**filter_kwargs)
Or you can build up the kwargs and have a single call to filter. Unless you are filtering over reverse FK relationships or M2M relationships, the two are pretty much the same (docs for when they are not the same are here)
filter_kwargs = {}
for key, val in request.POST.items():
if key in possible_filters:
filter_key = f'{key}__{possible_filters[key]}'
filter_kwargs[filter_key] = val
queryset = queryset.filter(**filter_kwargs)

Django-datatable-view throwing error after decorating url

I am using django-datable-view for rendering data from django models.
Everything works fine before decorating the url, after i added login_required to the url, it threw weird error.
According to the doc, it states that i can add login_required to the url.
Below is my code
from django_datatables_view.base_datatable_view import BaseDatatableView
class OrderListJson(BaseDatatableView):
# The model we're going to show
model = MyModel
# define the columns that will be returned
columns = ['number', 'user', 'state', 'created', 'modified']
# define column names that will be used in sorting
# order is important and should be same as order of columns
# displayed by datatables. For non sortable columns use empty
# value like ''
order_columns = ['number', 'user', 'state', '', '']
# set max limit of records returned, this is used to protect our site if someone tries to attack our site
# and make it return huge amount of data
max_display_length = 500
def render_column(self, row, column):
# We want to render user as a custom column
if column == 'user':
return '{0} {1}'.format(row.customer_firstname, row.customer_lastname)
else:
return super(OrderListJson, self).render_column(row, column)
def filter_queryset(self, qs):
# use parameters passed in GET request to filter queryset
# simple example:
search = self.request.GET.get(u'search[value]', None)
if search:
qs = qs.filter(name__istartswith=search)
# more advanced example using extra parameters
filter_customer = self.request.GET.get(u'customer', None)
if filter_customer:
customer_parts = filter_customer.split(' ')
qs_params = None
for part in customer_parts:
q = Q(customer_firstname__istartswith=part)|Q(customer_lastname__istartswith=part)
qs_params = qs_params | q if qs_params else q
qs = qs.filter(qs_params)
return qs
url
url(_(r'^users/all/?$'),
login_required(dashboard.v1.views.OrderListJson.as_view()),
name='all_users'),
i keep getting error 500, if i remove the login_required, everything works well. If i can get suggestions on how to decorate the class view, i will be glad since that is what am trying to achieve

Django ORM, how to use values() and still work with choicefield?

I am using django v1.10.2
I am trying to create dynamic reports whereby I store fields and conditions and the main ORM model information into database.
My code for the generation of the dynamic report is
class_object = class_for_name("app.models", main_model_name)
results = (class_object.objects.filter(**conditions_dict)
.values(*display_columns)
.order_by(*sort_columns)
[:50])
So main_model_name can be anything.
This works great except that sometimes associated models of the main_model have choicefield.
So for one of the reports main_model is Pallet.
Pallet has many PalletMovement.
My display columns are :serial_number, created_at, pallet_movement__location
The first two columns are fields that belong to Pallet model.
The last one is from PalletMovement
What happens is that PalletMovement model looks like this:
class PalletMovement(models.Model):
pallet = models.ForeignKey(Pallet, related_name='pallet_movements',
verbose_name=_('Pallet'))
WAREHOUSE_CHOICES = (
('AB', 'AB-Delaware'),
('CD', 'CD-Delaware'),
)
location = models.CharField(choices=WAREHOUSE_CHOICES,
max_length=2,
default='AB',
verbose_name=_('Warehouse Location'))
Since the queryset will return me the raw values, how can I make use of the choicefield in PalletMovement model to ensure that the pallet_movement__location gives me the display of AB-Delaware or CD-Delaware?
Bear in mind that the main_model can be anything depending on what I store in the database.
Presumably, I can store more information in the database to help me do the filtering and presentation of data even better.
The values() method returns a dictionary of key-value pairs representing your field name and a corresponding value.
For example:
Model:
class MyModel(models.Model):
name = models.CharField()
surname = models.CharField()
age = models.IntegerField()
...
Query:
result = MyModel.objects.filter(surname='moutafis').values('name', 'surname')
Result:
< Queryset [{'name': 'moutafis', 'surname': 'john'}] >
You can now manipulate this result as you would a normal dictionary:
if main_model_name is 'PalletMovement':
# Make life easier
choices = dict(PalletMovement.WAREHOUSE_CHOICES)
for item in result:
item.update({
pallet_movement__location: verbal_choice.get(
pallet_movement__location, pallet_movement__location)
})
You can even make this into a function for better re-usability:
def verbalize_choices(choices_dict, queryset, search_key):
result = queryset
for item in result:
item.update({ search_key: choices_dict.get(search_key, search_key) })
return result
verbal_result = verbalize_choices(
dict(PalletMovement.WAREHOUSE_CHOICES),
result,
'pallet_movement__location'
)
I suggest the use of the update() and get() methods because they will save you from potential errors, like:
The search_key does not exist in the choice_dict then get() will return the value of the search_key
update() will try to update the given key-value pair if exists, else it will add it to the dictionary.
If the usage of the above will be in the template representation of your data, you can create a custom template filter instead:
#register.filter(name='verbalize_choice')
def choice_to_verbal(choice):
return dict(PalletMovement.WAREHOUSE_CHOICES)[choice]
Have an extra look here: Django: How to access the display value of a ChoiceField in template given the actual value and the choices?
You would use get_foo_display
In your template:
{{ obj.get_location_display }}
or
{{ obj.pallet_movement.get_location_display }}
[Edit:] As pointed out in the comments this will not work when calling values()
an alternative to create a templatetag is :
{{form.choicefield.1}}
This shows the value of the initial data of the foreign key field instead the id.
The universal solution for any main_model_name is by Django Model _meta API introspection: class_object._meta.get_field(field_name).choices
That is:
choice_dicts = {}
for field_name in display_columns:
choice_dicts[field_name] = {
k: v for k, v in class_object._meta.get_field(field_name).choices
}
out = []
for row in results:
out.append({name: choice_dicts[name].get(value, value)
for name, value in row.items()
})
The rest is a trivial example, mostly copied code from the question
>>> pallet = app.models.Pallet.objects.create()
>>> palletm = app.models.PalletMovement.objects.create(pallet=pallet, location='AB')
>>>
>>> main_model_name = 'PalletMovement'
>>> conditions_dict = {}
>>> display_columns = ['pallet_id', 'location']
>>> sort_columns = []
>>>
>>> class_object = class_for_name("app.models", main_model_name)
>>> results = (class_object.objects.filter(**conditions_dict)
... .values(*display_columns)
... .order_by(*sort_columns)
... )[:50]
>>>
>>> # *** INSERT HERE ALL CODE THAT WAS ABOVE ***
>>>
>>> print(out)
[{'location': 'AB-Delaware', 'pallet_id': 1}]
It works equally with 'pallet_id' or with 'pallet' in display_columns. Even that "_meta" starts with underscore, it is a documented API.

Returning multiple fetched users

I have a view which fetches multiple users from a database based on passed in skills. It works almost as desired except if it returns more than one user it only passes back the most recently fetched user. How do I aggregate fetched users to be passed back to the template. I've tried passing them back as a list but they didn't appear.
Here is my code:
form = FilterFreelancerForm(request.POST)
filtered_skills = set((request.POST.getlist('skills_select')))
match_fl = Freelancer.object.annotate(c=Count('skills')).filter(c=len(filtered_skills))
candidate_freelancers = None
for skill in filtered_skills:
candidate_freelancers = match_fl.filter(skills=skill)
freelancers = None
for freelancer in candidate_freelancers:
freelancers = User.objects.filter(freelancer=freelancer.id)
return render(request, 'freelancestudent/browsefreelancers.html', {'freelancers': freelancers,
'filter_form': form})
I previously had this:
freelancers = []
for freelancer in candidate_freelancers:
freelancers.append(User.objects.filter(freelancer=freelancer.id))
which returns nothing to the template.
Instead of:
for freelancer in candidate_freelancers:
freelancers = User.objects.filter(freelancer=freelancer.id)
try:
freelancers = User.objects.filter(freelancer__in=[freelancer.id for freelancer in candidate_freelancers])
out:
[<User: user1>, <User: user2>]

Categories