Action vise versa in Admin Django - django

i write an action for class in admin.py
class YarnsAdmin(admin.ModelAdmin):
actions = [make_stockable_unstockable]
i want this action to change status vise versa of stockable for the product.
my try is:
def make_stockable_unstockable(self, request, queryset):
for product in queryset:
if product.stockable:
queryset.filter(id=product.id).update(stockable=False)
else:
queryset.filter(id=product.id).update(stockable=True)
self.message_user(request, "Position(s) were updated")
it works, but i think this takes a lot of resources.
if anyone has an idea to optimize it?

Since Django 1.8, Conditional Expressions (SQL's Case..When..) are supported.
Thus the following django ORM single update statement should accomplish what you need
from django.db.models import Case, When, F, Q, BooleanField, Value
queryset.annotate(new_value=Case(
When(Q(stockable=False), then=Value(True)),
default=Value(False),
output_field=BooleanField()
)).update(stockable=F('new_value'))
it generates the following sql
UPDATE `yourmodel`
SET `stockable` = CASE WHEN `yourmodel`.`stocakble` = 0 THEN 1 ELSE 0 END
WHERE <queryset's filters>
for the record, here is the original, wrong solution I initially proposed
you could issue just two updates instead of looping:
queryset.filter(stockable=False).update(stockable=True)
queryset.filter(stockable=True).update(stockable=False)
which will flip the flag with two update statements

You can try to use the bulk update which fires single query for bunch of records instead of one query per record.
def make_stockable_unstockable(self, queryset):
queryset.filter(stockable=False).update(stockable=True)
queryset.filter(stockable=True).update(stockable=False)

Related

Queryset in django admin action is always empty

I wrote an action that is supposed update a field in every record selected by the user according to this document.
The problem is that queryset is always empty and thus, no changes take place.
def SendCommandAction(self, request, queryset):
print(self._Command)
commandStatus = {
'Command':self._Command,
'Fetched':False
}
print(queryset) #output from console: <QuerySet []>
updated = queryset.update(Command_Status = commandStatus,)
self.message_user(request, ngettext(
'%d record was successfully updated.',
'%d records were successfully updated.',
updated,
) % updated, messages.SUCCESS)
After I select records and hit "Go" button this message appeares:
0 records were successfully updated.
I'm having a similar issue and found that this method options.response_action is responsable for handling the action and the queryset it gets.
In my case, I'm using mongodb, and that method response_action is overwriting my queryset (the method is filtering by objects pks, but for mongodb you need to provide ObjectId instances to the filter function and django admin use just strings).
if not select_across:
# Perform the action only on the selected objects
queryset = queryset.filter(pk__in=selected)
# `selected` here is a list of strings (and for mongodb should be bson.ObjectId)
# that is the reason the queryset is empty in my case
I solved this creating a custom queryset class for my model and overwrite the filter function carefully to convert list of strings to list of bson.ObjectIDs only if the filter includes pk on it.
I think you need to debug this method and figure out what is happening in your case.
Good luck!
Luis.-

group rest api data by day and hour. Django rest framework

I'm super new to django and the rest API framework. I have a project that I am working on using both and vueJS for the front end. I need to serialize some data for a chart.
For one of the API end points I am trying to group the data like so:
"day_of_the_week": {
"9am":[{"job":".."}],
"10am":[{"job":"..."}],
"11am": [{"job": ".."}],
...
}
I am using a Job class, for reference this is how the jobs end point looks like: jobs-api
So instead of what i have on the picture I am creating a new endpoint where i will only show one object that contains the data for any given day. On the front end there is a chart with filters that let the user filter the jobs by the day they request. On load, when the user has given no day of the week, the end point will return the object of 'today'.
Because I am new to this, I have no idea where to do this, my initial thought was to filter on the views.py, but for now I have done it in the serializer which gives me the error "Object of type Job is not JSON serializable".
This is how the serializer looks like:
jobs-by-day-serializer
Clearly, there is something that I am not quite grasping, so any help would be appreciated.
EDIT:
this is my views.py now, I have added the filter for the queryset to filter by day, so I can filter by day now:
jobs_by_day_viewset
I think that the correct way of doing it would be to implement the model serializer and then define the views.
A good reason for implementing different serializers is to return different result for the same model. But in your case you just need an specific subset of Job so I think the view is a better choice.
I assume you already implement JobSerializer, and probably you have a JobViewSet to L/C/U/M over the model.
So, now you just need to implement a new view for these task. Something like this could work,
from jobs.models import Job
from jobs.serializers import JobSerializer
from rest_framework import generics
class JobByDayList(generics.ListCreateAPIView):
serializer_class = JobSerializer
def get_queryset(self):
# get the day from parameters
day_query = self.kwargs['day_query']
# here your logic to get desire queryset
return queryset
The answers given provides a solution for one part of the question.
I managed to solve the second part. Once I could filter my queryset by day of the week I still had to group every object by the hour in which it had been started.
This is how i solved it:
def get(self, request):
params = self.get_params()
queryset = Job.objects.filter(
dt_start__week_day=params['day'], dt_start__gte=params['dt_start'], dt_end__lte=params['dt_end'])
queryset_started = queryset.annotate(hour=ExtractHour('dt_start'))
queryset_ended = queryset.annotate(hour=ExtractHour('dt_end'))
started = []
ended = []
total_jobs =[]
for n in range(0, 23):
queryset_end = queryset_ended.filter(hour=n)
ended.append(round(queryset_end.count() / queryset.count() * 100, 2))
queryset3 = queryset_started.filter(hour=n)
started.append(round(queryset3.count() / queryset.count() * 100, 2))
for x in range(len(started)):
total_jobs.append(round(started[x]+ended[x], 2))
I used a viewset that extended from APIview instead of the modelviewsets so that i was able to return an array of integers.

Django custom validation before the data is saved (Enforce at the database level)

This is an extension from my post here preventing crud operations on django model
A short into to the problem , im currently using a package called django-river to implement a workflow system in my application. The issue is that they do not have a predefined 'start' , 'dropped' , 'completed' state. Their states are stored as a django model instance. This would mean that my application is unable to programmatically differentiate between the states. Therefore , the labels of these states has to be hardcoded into my program (Or does it? Maybe someone has a solution to this?)
Suppose that there is no solution to the issue other than hardcoding the states into my application , this would mean that i would have to prevent users from updating , or deleting these states that i have pre created initially.
My idea is to have a form of validation check within the django model's save method . This check would check that the first 3 instances of the State model is always start , deactivated and completed and in the same order. This would prevent the check from passing through whenever a user trys to change items at the ORM level.
However , it would seem that there is 2 issues with this:
I believe django admin doesn't run the model class save method
Someone is still able to change the states as long as the way they changed it does not pass through the save() method. AKA from the DB SQL commands
Although it is unlikely to happen , changing the name would 'break' my application and therefore i wish to be very sure that no one can edit and change these 3 predefined states.
Is there a fool proof way to do this?
My idea is to have a form of validation check within the django model's save method.
if i understand your description, maybe you can just override the save() function of your model like so:
class MyModel(models.Model):
[..]
def save(self, *args, **kwargs):
# Put your logic here ..
super(MyModel, self).save(*args, **kwargs)
I got the answer from django documentation
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
def validate_even(value):
if value % 2 != 0:
raise ValidationError(
_('%(value)s is not an even number'),
params={'value': value},
)
You can add this to a model field via the field’s validators argument:
from django.db import models
class MyModel(models.Model):
even_field = models.IntegerField(validators=[validate_even])
FYI: It is not really mandatory to use gettext_lazy and you can use just message as follows
from django.core.exceptions import ValidationError
def validate_even(value):
if value % 2 != 0:
raise ValidationError(
('%(value)s is not an even number'),
params={'value': value},
)

django - checking to see if filter returns anything in the queryset

I'm a newb to Django. I need to check to see if a queryset returns any values at all, and if not, to skip to the next item in the loop. I tried try.. except ObjectDoesNotExist and that's not working. If a filter doesn't find anything, what does it return? How do I check for it?
Here's the existing code:
def assign_family_riders(leg):
remaining_leg_riders = list(leg.riders.all())
for car in CarAssignment.objects.filter(leg=leg):
driver_family = car.driver.family
try:
riders = leg.riders.all().filter(family=driver_family)
except ObjectDoesNotExist:
continue
for rider in riders:
car.riders.add(rider)
remaining_leg_riders.remove(rider)
return remaining_leg_riders
You don't need to specifically check. If the filter doesn't return any objects, an EmptyQuerySet will be returned and the forloop will never be entered.
riders = leg.riders.filter(family=driver_family)
for rider in riders:
...
If you really want to, you could simply do:
riders = leg.riders.filter(family=driver_family)
if riders:
for rider in riders:
...
The ObjectDoesNotExist exception is only raised when you are trying to retrieve a particular record using get():
try:
rider = leg.riders.get(...)
except Rider.DoesNotExist:
...
As Timmy said in his answer, your loop will not be entered if the queryset returns nothing. On the other hand, if you really want to know the number of records a filter will return, you can call its count() method: CarAssignment.objects.filter(leg=leg).count()
This performs a SELECT COUNT(*) for you in the background without retrieving any records.
See here for more information.
The most efficient way to do this is to use exists() on the queryset before iterating over it or doing anything else that might perform a more intensive interaction with your database.
An example from the docs;
The most efficient method of finding whether a model with a unique field (e.g. primary_key) is a member of a QuerySet is:
entry = Entry.objects.get(pk=123)
if some_queryset.filter(pk=entry.pk).exists():
print("Entry contained in queryset")
Which will be faster than the following which requires evaluating and iterating through the entire queryset:
if entry in some_queryset:
print("Entry contained in QuerySet")
I'm pretty sure queryset returns nothing. You can probably check it using ./manage.py shell then see what is in riders.

django's commit_on_success has no effect on number of sql queries

I'm trying an experiment with bulk saving objects. Using Django Debug Toolbar, I can see how many sql queries are run. However, it seems that the decorator has no affect on the number of SQL queries - it stays the same number with or without the decorator. Should it be decreasing?
#transaction.commit_on_success()
def fastsave(queryset):
for t in queryset:
t.save()
def test(request):
fastsave(TimeEvent.objects.all())
return render_to_response('test.html', {})
No. The transaction decorators only affect when the queries are finalized, not how many are run.
You should use update vs save. See docs here https://docs.djangoproject.com/en/dev/topics/db/queries/#updating-multiple-objects-at-once