django how to access m2m relationship in template - django

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.

Related

Django how to implement a one-to-many self-dependent foreign key

I am implementing a User referral system, which existing users can refer other people to register an account with the link they provided. After the new user registers, the new user will be stored to the field 'referred_who' of the existing user.
I have tried using the following method:
class CustomUser(AbstractBaseUser):
...
referred_who = models.ManyToManyField('self', blank=True, symmetrical=False)
class ReferralAward(View):
def get(self, request, *args, **kwargs):
referral_id = self.request.GET['referral_id']
current_referred = self.request.GET['referred']
// referrer
user = get_user_model().objects.filter(referral_id=referral_id)
// user being referred
referred_user = get_user_model().objects.filter(username=current_referred)
for item in user:
previous_referred = item.referred_who
previous_referred.add(referred_user[0])
user.update(referred_who=previous_referred)
And I got the following error:
Cannot update model field <django.db.models.fields.related.ManyToManyField: referred_who> (only non-relations and foreign keys permitted).
I am not sure if this method even works. I have check the Django Admin backend and I realized the 'Referred who' field actually contains all the users. It seems that it only highlightes the user being referred instead of only showing the referred users.
Also, I tried to access the 'referred_who' field in the back-end and it returns 'None'.
Is there a way to stored the users in the 'referred_who' field so that I can see all of the user being referred and access them in the back-end? For instance:
referral_id = self.request.GET['referral_id']
user = get_user_model().objects.filter(referral_id=referral_id)
print(user[0].referred_who)
Can someone show me a better way to do it? Thanks a lot!
You ask how to create a 1-Many field, but in your models you're trying to create m2m. Just change field to FK.
referred_who = models.ForeignKey('self', blank=True).
In case you need to have multiple fks to the same model, you need to specify related_name as well. You can use name of the field for it. More in docs.

Django url manipulation - detail page with pk / id should not show based on attributes of that object - Best Practice

I have a django page that displays a list of links. Each link points to the detail page of the respective object. The link contains the pk/id of that object (something like ../5/detailObject/). The list is generated on the backend and has some filtering baked into it, e.g. only generate a link if that object has state x, etc.
Clicking on the links works, but users can still manipulate the url and pass a valid link with an incorrect state (a wrong pk/id is being handled with the get or 404 shortcut).
What is the best practice for handling this kind of scenario with django? Should that kind of filtering be placed in the object's model class instead of using function-based views as I do now?
Function based view:
If you want to restrict a set of objects to a particular user (for instance a user's orders), then you would need to set up the Order model to foreign key to the User model and then look up the order by both id and user:
views.py:
def get_order(request, id=0)
if request.method == 'GET':
try:
order = Order.objects.get(user=request.user, pk=id)
except Order.DoesNotExist:
return redirect(...)
And set up a url to handle:
url(r'^order/(?P<id>\d+)/$', views.get_order, name='get_order_by_id'),
As far as adding a slug field on the model after the fact, set up a second url:
url(r'^order/(?P<slug>[\w-]+)/$', views.get_order, name='get_order_by_slug')
And change the above view logic to first do a lookup by pk if pk is greater than 0 and then redirect back to the function using the slug from the looked up order (this assumes all looked-up records have slugs):
def get_order(request, slug='', id=0)
if request.method == 'GET':
try:
if id > 0:
order = Order.objects.get(user=request.user, pk=id)
return redirect(reverse('get_order_by_slug'), permanent=True, slug=order.slug)
order = Order.objects.get(user=request.user, slug=slug)
except Order.DoesNotExist:
return redirect(...)
You should also put unique=True on the slug field and ensure that the user is authenticated by placing the #login_required decorator on your view.
To restrict orders by a particular status, you could:
Create a set of statuses for your Order model, and then you could:
Pass a value for a kwarg in the view when you filter, or
Create a custom manager on the Order model
There are several ways you could create your statuses:
as a set of choices on the Order model
use the SmartChoices library
as a database field
If you create choices on the Order model, it could be something like this:
class Order(models.model):
STATUSES = (
('PLCD', 'Placed'),
('INTR', 'In Transit'),
('DLVR', 'Delivered')
)
status = models.CharField(max_length=4, default='', choices=STATUSES)
An acquaintance who is a very seasoned Django professional told me about the SmartChoices library. I have not used it yet but would like to try it at some point. The database field option would be my least preferred way of doing this because that seems to me like moving programming variables into the database; however, it would work.

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.

django model attribute field empty list

I'm trying to build an online forum. Right now, my forum model has several attributes and one of it is "owner", which is a ForeignKey to the user that created this forum. It also has another attribute "passcode" which makes sure that whenever an owner creates a forum, he/she has to type a passcode so that only others with the right passcode can join the forum. Now, I am trying to implement a new function such that users can choose to join existing forums; however, I am stuck.
1) My first issue is that in order to create a custom permission, I first need another model attribute that contains a list of the permissioned users. I was thinking of having a model attribute as an empty list, permissioned_users = [], so that whenever a user requests to join a forum and has the right passcode, his/her username will be appended to the list and then in my views.py I can use #user_passes_test to check if the request.user.username is in the list. However, i'm not sure if "students = []" will work such that i can do "anyparticularinstance".students.append("his name") will work.
2) How do i create a join forum function? I have fully implemented a create forum function but how do I allow users to join an existing forum? Thank you!
One Way you can achieve the permissions is by defining a Boolean field in your model, for example:
class Forum(AbstractBaseUser):
username=models.CharField(max_length=20,unique=True)
name = models.CharField(max_length=20)
email = models.EmailField(max_length=254,null=True,blank=True)
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)
is_staff = models.BooleanField(default=False)
By extending the AbstractBaseUser in Django you can define custom permissions for users.Either from the default Admin provided by Django or may be your own custom admin, you can add or remove permissions to a particular user. For more information you can see the following link Django AbstractBaseUser
You can achieve your objective by using textfield, and then appending at the end of the textfield everytime you update the field.
Model:
permissioned_users = models.TextField(blank=True, null=True)
View:
foo = Forum.objects.get(id=id)
temp = foo.permissioned_users
temp = temp+" username"
foo.permissioned_users = temp
foo.save()
Obviously you have to do some more work, ex. when you want to check which user is given permission, you split the string using whitespace hence str.split(), then you can easily iterate through it and make your checks.

Check if model instance is FKed inside a queryset?

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()