Annotate non-model attribute in Django queryset - django

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.

Related

DRF Create a custom relational data or smth similar?

I have three models, one called Events and one called Tasks. Each one has a field called deadline.
Goal: I need to create a section in my Custom User model called Todis and a section called Overdose where I filter the Events and tasks that has deadline less than now and put them in the todos and I need to filter the other Tasks and Events object to put them in the overdoes.
from Myapp import Events
from MyOtherApp import Tasks
User(models.Model):
field1 = models...
field2 = models...
def smthing(self,args,kwargs);
# here create new field
# Events and Tasks are other models
for event in Events.objects.all():
if event.deadline < now():
self.Ovedows.append(event)
for tasks in Tasks.objects.all():
if tasks.deadline < now():
self.Ovedows.append(tasks)
I know how to filter and all this stuff but I need an idea to get that data and set them inside the user module without creating a new data field together them, just like using the relational data
I think you need do something like this
from datetime import datetime
class User(models.Model):
#....
def overdue_tasks(self):
now = datetime.now()
return Tasks.objects.filter(deadline__lt=now)
def overdue_events(self):
now = datetime.now()
return Events.objects.filter(deadline__lt=now)
deadline__lt means deadline less than which is equivalent to deadline < now
Since you need them from 2 different models, you'll need to merge them in the serializer or somewhere in the front end
Note that this currently fetches ALL the events for ALL the users, you didn't share your models properly (didn't share the Tasks and Events models) so I don't know how you are linking them to the User model, you should have a foreign key in them pointing to the user model.
assuming your Tasks model looks like this
class Tasks(models.Model):
deadline = ...
user = models. ForeignKey(User)
the query will instead be
self.tasks_set.filter(deadline__lt=now)
# and
self.events_set.filter(deadline__lt=now)

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)

How to filter the options for a ForeignKey based on an other ForeignKey

I need to filter the options for a ForeignKey based on an other ForeignKey.
class LabValue(models.Model):
measurement = models.ForeignKey(
'LabMeasurement', on_delete=models.CASCADE)
unit = models.ForeignKey(
LabMeasurementUnit,
on_delete=models.CASCADE,
limit_choices_to={'parameter__id': self.measurement.parameter.id},
)
How can I retrieve self.measurement.parameter.id? If I manually enter an ID instead of self.measurement.parameter.id like for example "1" the query works.
def __str__(self):
return str(self.measurement.parameter.id)
also works as desired and returns e. g. 1 as result
I don't believe this is possible, not due to Django's ORM, but due to the way the database itself works. measurement is a ForeignKey, so it's a relationship between two tables. But relationships between tables don't deal with filtering.
Thus, you will have to implement this at the level of the forms which update the model, or, alternatively, override the model's save function and throw an error if the desired expectation is not satisfied.
For a very clear answer: You can't do that.
Explanation:
It's a django model when you create model object/save then only your model object is created and on that time valid foreign key of specified instance is to be passed, so giving a limit to specific choices to model directly is not possible you need to handle it from view.
So if you have handle the choices of option in view but as of alternatively you can override clean() method to raise errors.
in model you can use clean method as below:
def clean(self):
if self.measurement.parameter.id == 1:
# .. do whatever you want to write if this condition happens
pass
elif self.measurement.parameter.id in [1,2,3,4,55,6]: # or just pass set of ids, statically or with queryset
# .. code
else:
raise Exception("Unexpected selection of choice!!!")

Django - How to save m2m data via post_save signal?

(Django 1.1) I have a Project model that keeps track of its members using a m2m field. It looks like this:
class Project(models.Model):
members = models.ManyToManyField(User)
sales_rep = models.ForeignKey(User)
sales_mgr = models.ForeignKey(User)
project_mgr = models.ForeignKey(User)
... (more FK user fields) ...
When the project is created, the selected sales_rep, sales_mgr, project_mgr, etc Users are added to members to make it easier to keep track of project permissions. This approach has worked very well so far.
The issue I am dealing with now is how to update the project's membership when one of the User FK fields is updated via the admin. I've tried various solutions to this problem, but the cleanest approach seemed to be a post_save signal like the following:
def update_members(instance, created, **kwargs):
"""
Signal to update project members
"""
if not created: #Created projects are handled differently
instance.members.clear()
members_list = []
if instance.sales_rep:
members_list.append(instance.sales_rep)
if instance.sales_mgr:
members_list.append(instance.sales_mgr)
if instance.project_mgr:
members_list.append(instance.project_mgr)
for m in members_list:
instance.members.add(m)
signals.post_save.connect(update_members, sender=Project)
However, the Project still has the same members even if I change one of the fields via the admin! I have had success updating members m2m fields using my own views in other projects, but I never had to make it play nice with the admin as well.
Is there another approach I should take other than a post_save signal to update membership? Thanks in advance for your help!
UPDATE:
Just to clarify, the post_save signal works correctly when I save my own form in the front end (old members are removed, and new ones added). However, the post_save signal does NOT work correctly when I save the project via the admin (members stay the same).
I think Peter Rowell's diagnosis is correct in this situation. If I remove the "members" field from the admin form the post_save signal works correctly. When the field is included, it saves the old members based on the values present in the form at the time of the save. No matter what changes I make to the members m2m field when project is saved (whether it be a signal or custom save method), it will always be overwritten by the members that were present in the form prior to the save. Thanks for pointing that out!
Having had the same problem, my solution is to use the m2m_changed signal. You can use it in two places, as in the following example.
The admin upon saving will proceed to:
save the model fields
emit the post_save signal
for each m2m:
emit pre_clear
clear the relation
emit post_clear
emit pre_add
populate again
emit post_add
Here you have a simple example that changes the content of the saved data before actually saving it.
class MyModel(models.Model):
m2mfield = ManyToManyField(OtherModel)
#staticmethod
def met(sender, instance, action, reverse, model, pk_set, **kwargs):
if action == 'pre_add':
# here you can modify things, for instance
pk_set.intersection_update([1,2,3])
# only save relations to objects 1, 2 and 3, ignoring the others
elif action == 'post_add':
print pk_set
# should contain at most 1, 2 and 3
m2m_changed.connect(receiver=MyModel.met, sender=MyModel.m2mfield.through)
You can also listen to pre_remove, post_remove, pre_clear and post_clear. In my case I am using them to filter one list ('active things') within the contents of another ('enabled things') independent of the order in which lists are saved:
def clean_services(sender, instance, action, reverse, model, pk_set, **kwargs):
""" Ensures that the active services are a subset of the enabled ones.
"""
if action == 'pre_add' and sender == Account.active_services.through:
# remove from the selection the disabled ones
pk_set.intersection_update(instance.enabled_services.values_list('id', flat=True))
elif action == 'pre_clear' and sender == Account.enabled_services.through:
# clear everything
instance._cache_active_services = list(instance.active_services.values_list('id', flat=True))
instance.active_services.clear()
elif action == 'post_add' and sender == Account.enabled_services.through:
_cache_active_services = getattr(instance, '_cache_active_services', None)
if _cache_active_services:
instance.active_services.add(*list(instance.enabled_services.filter(id__in=_cache_active_services)))
delattr(instance, '_cache_active_services')
elif action == 'pre_remove' and sender == Account.enabled_services.through:
# de-default any service we are disabling
instance.active_services.remove(*list(instance.active_services.filter(id__in=pk_set)))
If the "enabled" ones are updated (cleared/removed + added back, like in admin) then the "active" ones are cached and cleared in the first pass ('pre_clear') and then added back from the cache after the second pass ('post_add').
The trick was to update one list on the m2m_changed signals of the other.
I can't see anything wrong with your code, but I'm confused as to why you think the admin should work any different from any other app.
However, I must say I think your model structure is wrong. I think you need to get rid of all those ForeignKey fields, and just have a ManyToMany - but use a through table to keep track of the roles.
class Project(models.Model):
members = models.ManyToManyField(User, through='ProjectRole')
class ProjectRole(models.Model):
ROLES = (
('SR', 'Sales Rep'),
('SM', 'Sales Manager'),
('PM', 'Project Manager'),
)
project = models.ForeignKey(Project)
user = models.ForeignKey(User)
role = models.CharField(max_length=2, choices=ROLES)
I've stuck on situation, when I needed to find latest item from set of items, that connected to model via m2m_field.
Following Saverio's answer, following code solved my issue:
def update_item(sender, instance, action, **kwargs):
if action == 'post_add':
instance.related_field = instance.m2m_field.all().order_by('-datetime')[0]
instance.save()
m2m_changed.connect(update_item, sender=MyCoolModel.m2m_field.through)

django-admin action in 1.1

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