Django. How to restrict "profile" page to only "friends" - python

I currently have a "profile" page that displays specific users information they have uploaded. Currently it does so by
objects.filter(user = request.user)
Im trying to figure out how I can allow a, for lack of a better description, a "friend" to view someone else's profile. Cause right now, the user who makes the request gets their own information. I believe I know how to build the ability to "friend" another user... i just dont know how to display another users info since all ive been filtering on so far is "request.user"

You can do this using Many-to-many relationships
You object should look like this
class Profile(models.Model):
friends = models.ManyToManyField(Profile)
To check whether target profile belongs to your friend you can modify your code following way:
Profile.objects.filter(friends = request.user)

I would like to share how I implemented this in a project of mine. This may be somewhat specific for how I have implemented friend relationships, but I think the main idea should be the same.
Here is the view for view_profile
def view_profile(request, username):
if request.user.username == username:
return HttpResponseRedirect(reverse('accounts:profile'))
#get the user we are looking at
person = get_object_or_404(User, username=username)
#get the userprofile
person = person.userprofile
person_friend_object, person_created = Friend.objects.get_or_create(current_user=person)
user_friends = [friend for friend in person_friend_object.users.all()]
follower_count = len(user_friends)
friend = False
context = {
'person':person,
'friend':friend,
'follower_count':follower_count,
'user_friends':user_friends,
}
if request.user.is_authenticated():
friend_object, created = Friend.objects.get_or_create(current_user=request.user.userprofile)
friends = [friend for friend in friend_object.users.all()]
if person in friends:
friend = True
else:
friend = False
context['friend'] = friend
return render(request, 'users/user_profile_view.html', context)
Then, in the template you can control what friend can see of a given user's profile with template logic. Here's a basic example:
{% if not friend %}
<p>You are not friends with this user</p><button>Add friend</button>
{% else %}
<p>You are friends with this user. Here is information about this user...(here you can show data on the user through by accessing the `person` context variable)</p><button>Unfriend</button>
{% endif %}
So everything is controlled by the friend variable which is either True or False.
There are many ways to do what you are describing, this would be just one way I believe. Hope this helps with your project.

Related

Django - Pass Object ID after redirect

Apologies if the question title is wrong but I am unsure of exactly what I need to do. I am building a scrum app where users can create user stories and add them to sprints. I want to have two separate ways of creating stories. If the user is creating a story from the backlog, the story that has been created will not be assigned to a sprint, but the user can also add a story from a sprint's detail screen. I want to make it so that if they add a story from a specific sprint's page, that story's sprint field is populated with that sprint.
The way I have tried to go about this is by creating two different functions in my views.py file and two separate templates. I am able to create the story without relating to a sprint as intended, but I cannot think of a way to get the id of the sprint where I navigated from to get to the new template.
For reference I have included my function for creating stories without relating to a sprint:
def create_story(response, id=None):
user = response.user
if response.method == "POST":
form = CreateNewUserStory(response.POST)
if form.is_valid():
name = form.cleaned_data["name"]
description = form.cleaned_data["description"]
status = form.cleaned_data["status"]
assignee = form.cleaned_data["assignee"]
estimate = form.cleaned_data["estimate"]
userstory = UserStory(name=name, description=description, status=status, assignee=assignee, estimate=estimate)
userstory.save()
response.user.userstories.add(userstory)
return HttpResponseRedirect("/backlog")
else:
form = CreateNewUserStory()
return render(response, "main/create_story.html", {"form": form})
If any more information or code is required please let me know, thank you!
You can define 2 URLs, one with an optional sprint ID:
urls.py:
path('story/<int:sprint_id>/add/', views.create_story, name='create_story_for_sprint'),
path('story/add/', views.create_story, name='create_story_for_backlog'),
views.py:
def create_story(request, sprint_id=None):
...
form = CreateNewUserStory(response.POST, initial={"sprint_id": sprint_id})
if form.is_valid():
...
sprint_id = form.cleaned_data["sprint_id"]
create_story.html:
<form action="..." method="POST">
<input type="hidden" name="sprint_id" value="{{ sprint_id|default_if_none:'' }}">
...
Then, in your sprint page, use
Add Story
and in your backlog page:
Add Story

Implementing access control in django based on role

I need help in implementing access control on my django project. There are 2 main roles , sales and developer. In these 2 roles , there is another hierarchy , manager and non-manager. Based on their roles , I would want to display different things and do different types of queries.
The method I am using currently using is to extend my user model to include these roles , and using if statements within my template to display the functionalities accordingly.
Here is my model:
class UserProfile(models.Model):
role = (
('sm','sales_manager'),
('s','sales'),
('rm','rnd_manager'),
('r','rnd')
)
user = models.OneToOneField(User,on_delete=models.CASCADE)
user_type = models.TextField(max_length=500, choices= role)
contact = models.IntegerField(default=92388112)
def __str__(self):
return str(self.user.username)
Here is my view:
#login_required(login_url='login')
def rnd/home(request):
print(request.user.username)
context = {
'userProfile' : UserProfile.objects.all(),
}
return render(request, 'rnd/home.html',context)
here is a relevant part of my template:
{%if user.get_UserProfile.user_type == 's' or user.get_UserProfile.user_type == 'sm' %}
<p>Sales</p>
{%else%}
<p>RnD</p>
{%endif%}
<li>
However , my for loop does not work. It does not throw any error , but does nothing as well. When I'm logged in as a 'r' type , sales still gets shown on my screen.
It would be great if someone could answer me as well as leave some tips on the best way to implement such access control, not only in features but also in filtering the data shown in common features.
I don't see any for loop in your code. But if you just want UserProfile from User, you can get a OneToOne model directly from either way. In your case, it would be user.userProfile.user_type.
You might also want to look at Django Custom Permissions

Django partially restricting contents according to different user group

Just started Django last week and I am facing this problem regarding two types of users (normal and VIP) and how to restrict normal users from viewing VIP contents.
I am using Django with MySQL. My site does not requires user to signup but instead, I will be giving them username/password. I manually creates user via Django admin and group them according to VIP or normal. I will be uploading contents (again via admin) and I have a Boolean check-box that determine whether it is a VIP content or not.
What I done so far:
User must login to view the contents
Upon logging in, VIPs can see VIP contents and normal users will not be able to see VIP contents.
FAQ.html
{% extends 'base.html' %}
...skipping...
{% for faqs in fq reversed %}
{{ faqs.name }}
{{ endfor }}
faq_topics.html
<h2>{{ faqs.name }}</h2>
<h6 style="padding: 20px 20px 20px 20px">{{ faqs.description|linebreaksbr }}</h6>
urls.py
urlpatterns = [
path('faq/', views.faq, name='faq'),
url(r'^faq/(?P<pk>\d+)/$', views.faq_topics, name='faq_topics'),]
views.py
def is_user(user):
return user.groups.filter(name='User').exists()
def is_vip(user):
return user.groups.filter(name='VIP').exists()
#login_required
#user_passes_test(is_user)
def faq(request):
fq = FAQ.objects.filter(vip_only=False)
return render(request, 'faq.html', {'fq':fq})
models.py
class FAQ(models.Model):
name = models.CharField(max_length=30, unique=True)
description = models.CharField(max_length=300)
vip_only = models.BooleanField(default=False)
Error Case
3 FAQ entries are made, the first one is classified as VIP only.
When a normal user log in into FAQ section, he/she will only see 2 contents
Welcome to FAQ section
127.0.0.1:8000/faq/3/
127.0.0.1:8000/faq/2/
However, the user can easily guess that faq/1/ exists and when they actually tries, they can have access to vip contents too.
I have been searching the web for the past 2 days, I learn how to user #user_passes_test to restrict users and objects.filter(vip_only=False) to restrict contents.
Additionally, I wish to have my views.py controlling the contents for both kind of user but I have yet to implement that yet. Will be very grateful if someone can teach me how to implement "2 views in one templates".
I have read from,
show different content based on logged in user django
django - 2 views in one template
My solution for splitting user
#login_required
def faq(request):
'''user is separate using groups'''
if request.user.is_superuser or request.user.groups.filter(name='VIP').exists():
fq = FAQ.objects.all()
else:
fq = FAQ.objects.filter(vip_only=False)
return render(request, 'faq.html', {'fq':fq})
You should do something like this
def faq(request):
if request.user.is_superuser:
fq = FAQ.objects.all()
else:
fq = FAQ.objects.filter(vip_only=False)
return render(request, 'faq.html', {'fq':fq})
You can also use similar approach for doing things in the template:
{% if user.is_superuser %}
#...show what you only want to show the superuser
{% endif %}
And in your method for the single faq, do something like:
def faq_topics(request, pk):
faq_item=FAQ.objects.get(id=pk)
if not request.user.is_superuser and not request.user.groups.filter(name='VIP').exists():
if fqa_item.vip_only:
return render(request, 'path_to_access_error_template', {'fq':faq_nr})
return render(request, 'faq_item.html', {'fq':faq_nr})

django forms - how to update user data from previous comment when user posts a new comment

I feel like I'm really close, but not quite there yet. Please bear with me as I am very much so in the beginner stages of learning django.
I have a feature where users can comment on each blog post. I want to display the total number of comments each user has next to his name. If that user has left 4 comments on 4 different posts, I want it to show "4 total comments" next to his name on each one of his individual comments throughout my website.
I have made a model method that I put in my view, which automatically updates the total comments for each user. The only problem is that if the user has left two comments, only his latest comment will show "2 total comments". His previous one shows only "1".
My question is, how do I make the previous entry update when the user has left a new comment?
models.py
class Comment(models.Model):
...
post = models.ForeignKey(Post, related_name="comments")
user = models.ForeignKey(User, related_name="usernamee")
email = models.EmailField(null=True, blank=True)
picture = models.TextField(max_length=1000)
...
review_count = models.IntegerField(default=0)
class UserProfile(models.Model):
...
def user_rating_count(self): #This is what adds "1" to the user's total post count
user_ratings =
Comment.objects.all().filter(user_id=self.user.id).count()
user_ratings += 1
return user_ratings
views.py
#login_required
def add_comment(request, slug):
post = get_object_or_404(Post, slug=slug)
if request.method == 'POST':
form = CommentForm(request.POST)
if form.is_valid():
comment = form.save(commit=False)
comment.post = post
comment.user = request.user
comment.email = request.user.email
comment.picture = request.user.profile.profile_image_url()
comment.review_count = request.user.profile.user_rating_count() #This is where I'd like it to update, but it doesn't seem to work this way
comment.save()
return redirect('blog:post_detail', slug=post.slug)
else:
form = CommentForm()
template = "blog/post/add_comment.html"
context = {
'form': form,
}
return render(request, template, context)
template
{% for comment in post.comments.all %}
<p>{{ comment.user.first_name }} <b>{{ comment.user.last_name }}</b> {{ comment.review_count }}</p>
{% endfor %}
User comments once = FirstName LastName 1.
User comments twice, his second comment = FirstName LastName 2, but the first comment remains unchanged.
Any ideas on how to properly do this? Any help is greatly appreciated!
First i don't think you need the review_count to be a Database field. Except you plan to use it to sort (or do something that requires it to be in the Database).
From your question "django forms - how to update user data from previous comment when user posts a new comment" I believe you have an idea on why it's not working.
It's because it's a previous comment and Updating the data of the latest comment won't automatically update the previous comments (It would be disastrous if that happened by default :-) )
Anyway once you remove thereview_count and make user_rating_count a property of the UserProfile model your problem disappears.
class UserProfile(models.Model):
#property
def user_rating_count(self):
"""This is what adds "1" to the user's total post count"""
return self.usernamee.count() # Remember the `related_name` you set earlier?
Then you can use it in your templates like this
{% for comment in post.comments.all %}
<p>{{ comment.user.first_name }} <b>{{ comment.user.last_name }}</b> {{ request.user.profile.user_rating_count }}</p>
{% endfor %}
If you're bothered about the value being recalculated on each page load (You should be). Django provides a nifty decorator to cache the property in memory and reduce the load on your database (When you call the method/access the property repeatedly)
from django.utils.functional import cached_property
class UserProfile(models.Model):
#cached_property
def user_rating_count(self):
"""This is what adds "1" to the user's total post count"""
return self.usernamee.count() # Remember the `related_name` you set earlier?
If it doesn't need to be a Database field, it doesn't need to be. You can easily make it a computed property and Cache it (If you feel it's expensive to recalculate everytime and you don't really care about the "freshness" of the data).
By the way if you needed to update the Old comments (The way you wanted to initially), You would've done a batch update like this
I'm being overly verbose here:
comments = Comment.objects.filter(user_id=self.user.id)
count = comments.count()
comments.update(review_count=count)
That will update all the comments that match the filter params. But like i said i don't think this is the best approach for what you want to do.
Read up
https://docs.djangoproject.com/en/1.11/ref/utils/#django.utils.functional.cached_property
and
https://docs.djangoproject.com/en/1.11/ref/models/querysets/#update

Django Sessions not Working

I have built an application that shows users their storage usage and quotas on a system. Since there are many users, sifting through their storage allocations can be tedious so I want to give admins the option of acting as that user. The application decides which user is accessing the application based on an employee ID received via a secure badg, the variable (EMPLOYEE_ID) is stored in the request.META dictionary.
Ideally, I want the admins to be able to override this employee ID with another user's ID by posting it in a form. The form works and then serves the storage_home.html page as the employee the admin wishes to act as via a POST request, but when I or another admin clicks and does a GET for the quotas, the request.session dictionary is empty!
EMPLOYEE_ID is the original employee id of the admin
SIM_EMPLOYEE_ID is the employee the admin wishes to act as
I wonder if it's the way I'm linking to the quotas view in the storage_home.html template? Not sure.
Here is my code, I believe you should only need views, and the template that calls the quotas view function to see what the issue is since the request.sessions dictionary does have the SIM_EMPLOYEE_ID variable after the post that serves storage_home.html. I've omitted some variables from the views that are used in the template, but they work just fine, didn't want to clutter the code too much.
The sim_user function is called when the form is submitted. This then just recalls the storage function and right now successfully displays what I want it to, it's the GET request subsequently that fail to keep the session. I also have the following set in my settings:
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_DOMAIN = '.mydomain.com'
SESSION_SAVE_EVERY_REQUEST = True
SESSION_EXPIRE_AT_BROWSER_CLOSE = False
views.py
def home(request):
"""Redirect requests at root url to /storage"""
return HttpResponseRedirect('/storage/')
def storage(request):
"""Return the home template."""
context = {}
context.update(csrf(request))
empid = request.session.get('SIM_EMPLOYEE_ID')
if not empid:
empid = request.META.get('EMPLOYEE_ID')
if functions.is_admin(empid):
form = UserForm()
context['form'] = form
template = loader.get_template('storage_admin.html')
else:
template = loader.get_template('storage_home.html')
data = RequestContext(request, context)
return HttpResponse(template.render(data))
def sim_user(request):
context = {}
context.update(csrf(request))
if request.method == 'POST':
form = UserForm(request.POST)
if form.is_valid():
empid = form.cleaned_data['empid']
request.session['SIM_EMPLOYEE_ID'] = empid
request.session.modified = True
return storage(request)
template = loader.get_template('deny.html')
data = RequestContext(request, context)
return HttpResponse(template.render(data))
def quotas(request, sitename):
"""Return quota page depending on the
id of the employee. If employee is an
administrator, show all the quota information
for all users/projects. If employee is a user
of the sitename, show them user specific quota information.
Otherwise, deny access and display a custom template."""
context = {}
site = sitename.capitalize()
# EMPLOYEE_ID is in the Http Request's META information
empid = request.session.get('SIM_EMPLOYEE_ID')
if not empid:
empid = request.META.get('EMPLOYEE_ID')
if not empid:
template = loader.get_template('deny.html')
return HttpResponse(template.render(RequestContext(request, context)))
if functions.is_admin(empid):
template = loader.get_template('all_quotas.html')
else:
template = loader.get_template('personal_quotas.html')
data = RequestContext(request, context)
return HttpResponse(template.render(data))
storage_home.html
{% extends 'base.html' %}
{% block title %}Storage Utilization{% endblock %}
{% block content %}
<h1 id="header"><b>Storage Utilization</b></h1>
<p></p>
<table id="storage_table" cellspacing="15">
<tbody>
{% for site in sites %}
{% url "su.views.quotas" as quota %}
<tr>
<td><img src="/static/images/{{ site }}.png"></td>
</tr>
{% endfor %}
</tbody>
</table>
<br></br>
{% endblock %}
Thanks for any help, please let me know if you need more explanation, code, or simplification.
Turns out removing SESSION_COOKIE_SECURE = True fixed the issue. This is my fault for not forgetting that my dev environment uses http and prod https. I actually have separate settings files, but failed to use them properly when I went back to test this new feature. I believe setting the SESSION_COOKIE_SECURE to True when using https should work once I test the production server.
Django provided session stopped working for me for some reason. I made my own it's really easy:
models.py
class CustomSession(models.Model):
uid = models.CharField(max_length=256)
def __str__(self):
return self.uid
How to work with CustomSession
from oauth.models import CustomSession
session = CustomSession.objects # get a list of session objects
new_user = CustomSession(uid=<UID>) # save a user to the session (by uid)
session.get(id=<ID>).uid # get user id
session.get(id=<ID>).delete() # delete user from session (logout)
session.all().delete() # delete all user data in session

Categories