Check if model instance is FKed inside a queryset? - django

I have a model:
class ProjectBookmark(models.Model):
user = models.ForeignKey(User)
project = models.ForeignKey('Project')
say a user is viewing a project, is there a way to somehow check if current project is in users bookmarks other than a nasty way I do this now via request context as bellow? Basically, users bookmarks are a list of projects and checked as
{% if current_project in current_user.bookmarks %}...
right from inside the template?
the_user = request.user
bookmarked_projects = list()
for b in ProjectBookmark.objects.filter(user = request.user):
bookmarked_projects.append(b.project_id)
the_user.bookmarks = Project.objects.filter(id__in=bookmarked_projects)
return {'current_user':request.user}

For use directly in a template, the best way to go would be creating a custom template tag. Check out the template tag docs.
As for the filtering code itself, it would be very helpful to provide related_name arguments to the ForeignKey fields in the model definition. For example:
class ProjectBookmark(models.Model):
user = models.ForeignKey(User, related_name="project_bookmarks")
project = models.ForeignKey('Project', related_name="bookmarks")
This way you could access a QuerySet object containing all the users bookmarks using request.user.project_bookmarks.all()

Related

Django global variable based on user groups

In my Django project I have a database that is populated from outside Django, but needs the Django functionality to display data in the database in a user friendly manner. The legacy database structure is as follows:
class SomeModel(models.Model):
#some fields
group_access = models.ForeignKey(AccessGroup,on_delete=models.CASCADE()
class AccessGroup(models.Model):
group_name = models.CharField(max_length=200)
The users have a custom User profile with manytomany relationship with group names assigned to the specific user:
class CustomUser(models.Model):
#some values
projects = models.ManyToManyField(AccessGroup)
Currently I am able to display data from all groups a user has access to, but what I am looking for is a way to create a drop down menu so that users can switch between groups without the need to log out or reenter group on every view.
You could try something like this:
AccessGroup.objects.filter(CustomUser__pk=1)
Or
CustomUser.objects.filter(AccessGroup__group_name='GropName')
https://docs.djangoproject.com/en/2.0/topics/db/examples/many_to_many/
you can extend the django user model, somthing like
from django.contrib.auth.models import User
class CustomUser(models.Model):
projects = models.ManyToManyField(AccessGroup)
class UserProfile(models.Model):
user = models.OneToOneField(User, unique=True)
custom_user = models.ForeignKey(CustomUser, unique=False)
class SomeModel(models.Model):
#some fields
group_access = models.ForeignKey(AccessGroup,on_delete=models.CASCADE()
class AccessGroup(models.Model):
group_name = models.CharField(max_length=200)
then something like this to get the data in your view
def index(request):
AccessGroup.objects.filter(user__id=persion(request.user).id)
I'll assume you know how to get the list of groups, and are just looking as to how to get this list into templates. If not, let me know and I'll explain that as well.
If you're trying to get a global variable into templates, there are really 3 main options:
Make a custom template tag that takes the current user as input, and generates this list as output.
Use Middleware to generate the list, and append it to the current context for each request
Use a method on your user class, or a mixin of it (really easy if you use a custom user class), and just call that method as user.method in your templates. Remember to exclude parentheses from the method call (only in templates), and keep in mind that this method shouldn't accept any parameters other than self.
Thank you everybody for getting me on the right track. What I ended up doing is writing a context processor for checking the user permissions:
#context_processors.py
def check_groups(request):
group_check = AccessGroup.objects.values('id','group_name').filter(projects=request.user.id)
return {
'group_check': group_check,
}
Afterwards I created a Bootstrap-select dropdown in my base.html
<select class="selecpicker">
<optgroup>
<option data-hidden="true">Choose group</option>
{% for grpup in group_check %}
<option val="group.id">{{ group.group_name }}</option>
{% endfor %}
</optgroup>
And the it is just a matter of users using it as means to switch access groups in views and passing the value via ajax to any other template views I come across.
Not the 100% what I was looking for, but it works and my users are happy.

How to reference model joined across foreign key in django admin

I have a django 1.6 app with the following (trimmed for clarity)
classes defined. User is the standard django.contrib.auth User class.
class Event(models.Model):
user = models.ForeignKey(User, related_name='events')
name = models.CharField(max_length=64)
class Profile(models.Model):
user = models.ForeignKey(User, related_name='aprofile')
class MemberProfile(Profile):
pass
Here are my admin classes:
class ProfileAdmin(ModelAdmin):
model = Profile
fields = ('user', )
class MemberProfileAdmin(ModelAdmin):
model = MemberProfile
fields = ('user', )
readonly_fields = ('user', )
What I'd like to do is display a read-only list of all events for a given member, or at least profile. Of course joining across the User foreign key seems like the way to go, but I am drawing a blank as to how to accomplish this. Here's a summary of attempts so far.
Define an inline admin on the Event class directly referencing the user field, and add it to the ProfileAdmin:
class EventInlineAdmin(TabularInline):
model = Event
fk_name = 'user' # Fails - fk_name 'user' is not a ForeignKey to <class 'solo365.solo_profile.models.profile.Profile'>
...well, no, it sure isn't. But our User has an 'aprofile' field, so...
class EventInlineAdmin(TabularInline):
model = Event
fk_name = 'user__aprofile' # Fails - EventInlineAdmin.fk_name' refers to field 'user__aprofile' that is missing from model 'admin_fk_test.Event'.
Ok, those fields look like they should sync up, but perhaps we need to be a little more aggressive:
class EventInlineAdmin(TabularInline):
model = Event
fk_name = 'user__aprofile__pk' # Fails - 'EventInlineAdmin.fk_name' refers to field 'user__aprofile__pk' that is missing from model 'admin_fk_test.Event'.
I've also tried messing with formfield_for_foreignkey() and friends in both the inline and the regular model admins, but without that fk_name having a valid value, those methods never get called.
I then considered trying to access the events field directly from a Profile's user:
class ProfileAdmin(ModelAdmin):
model = Profile
fields = ('user', 'user__events') # Fails - Unknown field(s) (user__events) specified for Profile. Check fields/fieldsets/exclude attributes of class ProfileAdmin.
What about with a custom formfield_for_foreignkey() method? Sadly that never gets called for anything other than the 'user' field. I've also considered a custom get_formsets() method, but frankly I'm not sure how I could use that without a working EventInlineAdmin.
I could of course define a custom field that simply concatenates all of the events and returns that as a string, but ideally I would prefer something like a fully-featured inline (even read-only) than just a chunk o' text. IOW such a custom field would have a method that (ideally) would return an inline form without requiring any sort of custom template, setting of allow_tags, etc.
Am I doomed to have to create a completely custom Form for the Profile admin class? Or is there a simple way to accomplish what I'm trying to do, that I'm just missing?
Update:
Bonus points if a provided solution works for the MemberProfileAdmin class, not just the ProfileAdmin class.
The relation between User and Profile should be a 1:1 relation which would allow the referencing via user__aprofile. Otherwise, the reverse relation of a foreing key is a queryset because one foreign key can be assigned to multiple instances. This is might be the reason why your code failed.
Change it to:
class Profile(models.Model):
user = models.OneToOneKey(User, related_name='aprofile')
This is a bit like using ForeignKey(unique=True).
To know the attributes, it might help to call dir(model_instance) on the model instance in question, or try around in the Django shell (./manage.py shell).
Also, I've experienced that it might be more confusing to assign a custom related_name like in your case where you would expect one profile by looking at the related name but you would actually get back a queryset.
The generated name in that case would be profile_set, and you would have to call profile_set.all() or profile_set.values() to get some actual profiles.

django how to access m2m relationship in template

In my app, users can link profiles. In a sidebar that is visible on all pages of the site, I want to display the usernames of profiles a user is linked to. So far I have created an m2m field to link the profiles, and when a user logs in, I store this information in a session so it can be bundled with other session information and doesn't create another variable that has to be explicitly passed to each template. However, when accessing the list of linked profiles, I can only access the id's of the profiles, and not any other information about them.
model
class Profile(models.Model):
username = models.CharField(max_length=25)
link = models.ManyToManyField('self', null=True, blank=True, related_name='link_profiles')
view
def link_profiles(request, pid):
#get both profiles
my_p = Profile.objects.get(id=request.session['profile']['id'])
their_p = Profile.objects.get(id=pid)
#add profiles to eachothers links
my_p.link.add(their_p)
their_p.link.add(my_p)
#save profiles
my_p.save()
their_p.save()
#reset my session var to include the new link
#this is that same bit of code that sets the session var when the user logs in
request.session['profile'] = model_to_dict(my_p)
return redirect('/profiles/' + pid)
template (using pyjade)
- for profile in session.profile.link
div
a(href="/profiles/{{ profile }}") profile {{ profile }}
This will output something like <a href='/profiles/5'>profile 5</a>, but using profile.id and profile.username just puts blank space in <a href='/profiles/'>profile</a>. Is it possible to access this information in this way without having to create another session variable (say request.session['links'])?
model_to_dict will only give you a list of private keys (ids) and not all the data of the related objects.
That means you'll need to create that 'links' session variable by iterating through each of the related objects:
request.session['links'] = [model_to_dict(link) for link in my_p.links.all()]
If you want to optimize that, you could use sets, and only add the new profile:
data = model_to_dict(their_p)
if 'links' in request.session:
request.session['links'].add(data)
else:
request.session['links'] = set([data])
That should do it, but I think it may not be the best way. I'm not familiarized with PyJade, but I would pass the queryset returned by my_p.links.all() to the template in a context and iterate that instead.
Anyway, I hope that works for you.

Django Admin Form Validation from QuerySet

I have these two models:
class Service(MelosModel):
performer = models.ForeignKey(Performer)
event = models.ForeignKey('Event')
composition = models.ForeignKey(Composition)
class Event(MelosModel):
event_type = models.ForeignKey('EventType')
project = models.ForeignKey(Project)
works = models.ManyToManyField(Work)
date_of_event = models.DateTimeField()
location = models.ForeignKey(Address)
Note: A MelosModel is for all intents and purposes the same as models.Model. Also, Composition extends Work.
The trouble is that the list of compositions in the Service admin form needs to be validated against the available Works from its Event. How do you do this?
I read about making a ModelChoiceField from a queryset but that wouldn't help because we don't know what the Event is until the form is submitted. What is the best way to deal with this?
If I understood correctly you could write a clean() method on you Service model class to do the custom validation.

How to easilly get the name of the admin which created an item in the template

Is there an easy way to reference the creator (admin)'s name of an item from within a template.
If not, what is the easiest method of passing the value through from the view?
Add a foreign key to the user model e.g.: creator = models.ForeignKey('auth.User', null=True)
Overload modeladmin.save_model() method to set obj.creator to request.user as in the documentation
Access youritem.creator in the template e.g.: {{ item.creator }}
Django doesn't store the creator automatically, you'll need to add a field to your model and store it yourself when you create the object.
class MyModel(models.Model):
# stuff
creator = models.ForeignKey(User)
Then in your template, you can access it like this...
{{ my_object.creator }}