django-admin action in 1.1 - django

I am writing a action in django.I want to now about the rows which are updated by the action or say id field of the row.I want to make a log of all the actions.
I am having a field status which has 3 values :'activate','pending','reject'.I have made action for changing the status to activate.when i perform the action i want to have the log of rows updated so i need some value which can be stored in log such as id coresponding to that row

As far as i can understand you want make an admin log-entry for the object you update using your custom action. I actually did something like that, purely as django does it. As its your custom action you can add this piece of code.
Edit: Call this function after your action finishes, or rather i should say, after you change the status and save the object.
def log_it(request, object, change_message):
"""
Log this activity
"""
from django.contrib.admin.models import LogEntry
from django.contrib.contenttypes.models import ContentType
LogEntry.objects.log_action(
user_id = request.user.id,
content_type_id = ContentType.objects.get_for_model(object).pk,
object_id = object.pk,
object_repr = change_message, # Message you want to show in admin action list
change_message = change_message, # I used same
action_flag = 4
)
# call it after you save your object
log_it(request, status_obj, "Status %s activated" % status_obj.pk)
You can always get which object you updated by fetching LogEntry object
log_entry = LogEntry.objects.filter(action_flag=4)[:1]
log_entry[0].get_admin_url()
Hope this helps.

It is very easy!
Just make a loop of your queryset, then you can access each field of that row and store it where you want.
for e in queryset:
if (e.status != "pending"):
flag = 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.-

Flask-admin editable select column row-based filter

I am using flask-admin to have easy edits on my DB model. It is about renting out ski to customers.
This is my rental view (the sql_alchemy DB model is accordingly):
class RentalView(ModelView):
column_list = ("customer", "from_date", "to_date", "ski", "remarks")
...
customer and ski are relationship fields to the respective model. I want to only show these ski in the edit view that are not rented by others in this time period.
I have been searching everywhere how to dynamically set the choices of the edit form but it simply does not work fully.
I tried doing
def on_form_prefill(self, form, id):
query = db.session.query... # do the query based on the rental "id"
form.ski.query = query
and this correctly shows the filtered queries. However, when submitting the form, the .query attribute of the QuerySelectField ski is None again, hence leading to a query = self.query or self.query_factory() TypeError: 'NoneType' object is not callable error. No idea why the query is being reset?!
Does anybody know a different strategy of how to handle dynamic queries, based on the edited object's id?
Use this pattern, override the view's edit_form method, instance the default edit form (by calling the super() method then modify the form as you wish:
class RentalView(ModelView):
# setup edit forms so that Ski relationship is filtered
def edit_form(self, obj):
# obj is the rental instance being edited
_edit_form = super(RentalView, self).edit_form(obj)
# setup your dynamic query here based on obj.id
# for example
_edit_form.ski.query_factory = lambda: Ski.query.filter(Ski.rental_id == obj.id).all()
return _edit_form

Update last inserted record in Django

In Django, I want to retrieve the last inserted record from the database and update its values.
I use this model:
def User(models.Model):
name = models.CharField(max_length=15)
I run the following code to retrieve the last inserted record and update the name:
User.objects.last().update(name=‘NEW NAME’)
However, the error is that update is not a known method.
Does .last() indeed return the entire record, or only the primary key?
Thank you very much.
Does .last() indeed return the entire record, or only the primary key?
.last() [Django-doc] returns the last User object, or None, if there is no such record.
Now a single model object has indeed no .update(..) method. Only a QuerySet has an update(..) [Django-doc] method. You thus can for example retrieve it, alter the field, and then save the object:
last_user = User.objects.last()
if last_user is not None:
last_user.name = 'NEW NAME'
last_user.save()
you must at first get the user model then use last like this
user = User.objects.all().last()
and now you can update with this code
user.name = 'Daniel'
user.save()
Suppose you model/database is this
class User(models.Model):
name = models.CharField(max_length=15)
Then you go to the terminal and start the shell to do some testing. For that you do
python manage.py shell
In the shell, first you need to import your database/model
from nameofyourapp.models import User
To get the last element you could write
lastUser = User.objects.all().last()
Now change the last User
lastUser["name"] = "Josh"
Save the changes
lastUser.save()
Exit from the terminal

How to Filter ModelChoiceFilter by current user using django-filter

I'm using django-filter which is working great but I am having a problem filtering my drop down list of choices (which is based on a model) by the current user. It's a fairly basic and common scenario where you have a child table which has a many to one relationship to a parent table. I want to filter the table of child records by selecting a parent. This is all fairly easy, standard stuff. The fly in ointment is when the parent records are created by different users and you only want to show the parent records in the drop down list that belongs to the current user.
Here is my code from filters.py
import django_filters
from django import forms
from .models import Project, Task
from django_currentuser.middleware import get_current_user, get_current_authenticated_user
class MasterListFilter(django_filters.FilterSet):
project = django_filters.ModelChoiceFilter(
label='Projects',
name='project_fkey',
queryset=Project.objects.filter(deleted__isnull=True, user_fkey=3).distinct('code')
)
class Meta:
model = Task
fields = ['project']
#property
def qs(self):
parent = super(MasterListFilter, self).qs
user = get_current_user()
return parent.filter(master=True, deleted__isnull=True, user_fkey=user.id)
This bit works fine:
#property
def qs(self):
parent = super(MasterListFilter, self).qs
user = get_current_user()
return parent.filter(master=True, deleted__isnull=True, user_fkey=user.id)
This filters my main list so that only records that have a master flag set, have not been deleted and belong to the current user are shown. This is exactly what I want.
This following bit also works and gives me the filtered drop down list that I am looking for because I have hardcoded 3 as the user.id
queryset=Project.objects.filter(deleted__isnull=True, user_fkey=3).distinct('code'),
Obviously I don't want to have a hardcoded id. I need to get the value of the current user. Following the same logic used for filtering the main table I end up with this.
class MasterListFilter(django_filters.FilterSet):
**user = get_current_user()**
project = django_filters.ModelChoiceFilter(
label='Projects',
name='project_fkey',
queryset=Project.objects.filter(deleted__isnull=True, user_fkey=**user.id**).distinct('code')
)
However this is unreliable as sometimes it shows the correct list and sometimes it doesn't. For example if I login and it's not showing the list (ie it shows just '---------') and then I restart my apache2 service, it starts to work again, then at some point it drops out again. Clearly this is not a long term solution.
So how do I reliably get the current user into my filter.py so that I can use it to filter my drop down filter list.
Thanks in advance and happy coding.
EDIT:
So following Wiesion's suggestion I changed my code as suggested but I still get a None Type Error saying that user has no attribute ID. BAsically it seems I'm not getting the current user. So going back to the docs and trying to merge their suggestion with Wiesion (whose explanation makes total sense - Thanks Wiesion) I came up with the following:
def Projects(request):
if request is None:
return Project.objects.none()
return lambda req: Project.objects.filter(deleted__isnull=True, user_fkey=req.user.id)
class MasterListFilter(django_filters.FilterSet):
project = django_filters.ModelChoiceFilter(
label='Projects',
name='project_fkey',
queryset=Projects
)
class Meta:
model = Task
fields = ['project']
This kind of works in theory but gives me nothing in the drop down list because
if request is None:
is returning True and therefore giving me an empty list.
So...can anyone see where I'm going wrong which is preventing me from accessing the request? Clearly the second portion of code is working based on qs that is passed from my view so maybe I need to pass in something else too? My view.py code is below:
def masterlist(request, page='0'):
#Check to see if we have clicked a button inside the form
if request.method == 'POST':
return redirect ('tasks:tasklist')
else:
# Pre-filtering of user and Master = True etc is done in the MasterListFilter in filters.py
# Then we compile the list for Filtering by.
f = MasterListFilter(request.GET, queryset=Task.objects.all())
# Then we apply the complete list to the table, configure it and then render it.
mastertable = MasterTable(f.qs)
if int(page) > 0:
RequestConfig(request, paginate={'page': page, 'per_page': 10}).configure(mastertable)
else:
RequestConfig(request, paginate={'page': 1, 'per_page': 10}).configure(mastertable)
return render (request,'tasks/masterlist.html',{'mastertable': mastertable, 'filter': f})
Thanks.
From the docs
The queryset argument also supports callable behavior. If a callable
is passed, it will be invoked with Filterset.request as its only
argument. This allows you to easily filter by properties on the
request object without having to override the FilterSet.__init__.
This is not tested at all, but i think something along these lines this is what you need:
class MasterListFilter(django_filters.FilterSet):
project = django_filters.ModelChoiceFilter(
label='Projects',
name='project_fkey',
queryset=lambda req: Project.objects.filter(
deleted__isnull=True, user_fkey=req.user.id).distinct('code'),
)
class Meta:
model = Task
fields = ['project']
Also if it's depending from webserver restarts - did you check caching issues? (In case, django-debug-toolbar gives great insights about that)
EDIT
The unpredictable behaviour most probably happens because you are retrieving the user within the class MasterListFilter definition, so get_current_user() is executed at class loading time, not during an actual request and all subsequent calls to qs will retrieve that query. Generally everything request-related should never be in a class definition, but in a method/lambda. So a lambda which receives the request argument and creates the query only then should exactly cover what you need.
EDIT 2
Regarding your edit, the following code has some issues:
def Projects(request):
if request is None:
return Project.objects.none()
return lambda req: Project.objects.filter(deleted__isnull=True, user_fkey=req.user.id)
This either returns an empty object manager, or a callable - but the method Project itself is already a callable, so your ModelChoiceFilter will receive only an object manager when the request object is None, otherwise a lambda, but it is expecting to receive an object manager - it can't iterate over a lambda so it should give you some is not iterable error. So basically you could try:
def project_qs(request):
# you could add some logging here to see what the arguments look like
if not request or not 'user' in request:
return Project.objects.none()
return Project.objects.filter(deleted__isnull=True, user_fkey=request.user.id)
# ...
queryset=project_qs
# ...
As stated in the following thread, you have to pass the request to the filter instance in the view: Customize queryset in django-filter ModelChoiceFilter (select) and ModelMultipleChoiceFilter (multi-select) menus based on request
ex:
myFilter = ReportFilter(request.GET, request=request, queryset=reports)

Annotate non-model attribute in Django queryset

I have a model Event which has a ManyToManyField named users. I want to get all the events with an extra attribute which denotes whether a specific user belongs to this event or not.
class Event(models.Model):
users = models.ManyToManyField(User)
# Expected code format
user = User.objects.get(id=1)
events = // Event query
print events[0].your_event // It should be True if above user is part of this event else False
Here is the code which I tried
from django.db.models import Case, When, BooleanField
user = User.objects.get(id=1)
Event.objects.annotate(your_event=Case(when(users=user, then=True), default=False, output_field=BooleanField()))
Problem with above code: If an Event object has multiple users (let 4) then it is returning 4 objects which has your_event as False in three objects and True in one object. But I want only object for each
event.
I believe a better solution can exist for this purpose.