Django user authentication and rendering content created by that specific user? - python

I am creating a very simple to-do list application. Each user should have an associated to-do list page with basic CRUD functionality. That means User A should have different data than User B.
As of right now there is no distinction about who owns the to-do list. Anyone who is logged in can add, remove, display, delete tasks.
I have a gut feeling that I might need an extra something in my model and my template. I should mention that I use Pinax 0.9a2. If it does what I need it to do, I would prefer to use that solution instead.
Here's my models.py
class Task(models.Model):
name = models.CharField(max_length=100)
added_at = models.DateTimeField(auto_now_add=True)
last_update = models.DateTimeField(auto_now=True)
def __unicode__(self):
return self.name
Here's one of my forms in views.py
def task_create(request):
return create_object(request,
model=Task,
template_name='task_create.html',
post_save_redirect=reverse("todo_list")
)
Here's 2 of my templates:
To-Do Create
<form action="" method="post">{% csrf_token %}
{{ form.name }}
<button type="submit" class="btn primary">Create →</button>
</form>
To-Do List
{% if task_list %}
<p>Create a task</p>
<ul>
{% for task in task_list %}
<li>{{ task.name }}</li>
{% endfor %}
</ul>
{% else %}
<p>No tasks to display, click to create new.</p>
{% endif %}

So you just want to add access control to it?
Add ForeignKey to auth.User to your ToDos model
Rewrite your list and create views to make their work manually (you can achieve your goal with new class based views but you need to understand how do they work first)
Add filter for queryset in list view
Provide commit=False to your form's save(), set up user for retrieved object and save it manually
Code:
class Task(models.Model):
user = models.ForeignKey('auth.User')
name = models.CharField(max_length=100)
added_at = models.DateTimeField(auto_now_add=True)
last_update = models.DateTimeField(auto_now=True)
class TaskForm(forms.ModelForm):
class Meta:
model = Task
exclude = ['user', ]
def task_create(request):
form = TaskForm(data=request.POST or None)
if request.method == 'POST' and form.is_valid():
task = form.save(commit=False)
task.user = request.user
task.save()
return reverse("todo_list")
return render(request,
'task_create.html',
{'form': form}
)
Also, add filtering by request.user in list view and I'd recommend #login_required decorator to avoid adding tasks by non-authorized users.

Related

First argument to get_object_or_404() must be a Model. How can I get an user's id to the User model?

I'm trying to make a favorite functionality where an user can add other users as their favorites.
In the View where the profile of an user is shown I have a button that adds an user or removes it if it was already added.
The problem is that I can't pass to the views the user that will be added as a favorite.
models.py
class User(AbstractUser):
is_type1 = models.BooleanField(default=False)
...
class Type1(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
favorite = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, related_name='favorite')
views.py
def FavoriteView(request, pk):
current_user = request.user
Type1.user = current_user.id
buser = Type1.user
Type1.favorite = get_object_or_404(User, id=request.POST.get('username')) # The of the error where I try to add the user being added as a favorite
fuser = Type1.favorite
if Type1.favorite.filter(id=request.user.id).exists():
Type1.favorite.remove(request.user)
else:
Type1.favorite.add(request.user)
return HttpResponseRedirect(reverse('profile-details', kwargs={'username': Type1.favorite}))
class UserView(DetailView):
model = User
...
template_name = 'users/profile-details.html'
def get_context_data(self, **kwargs):
data = super().get_context_data(**kwargs)
favorite_connected = get_object_or_404(Type1.favorite, id=self.kwargs['username']) # The of the error where I try to add the user being added as a favorite
favorite = False
if favorite_connected.favorite.filter(id=self.request.user.id).exists():
liked = True
data['user_is_favorite'] = favorite
return data
profile-details.html
...
{% if user.is_authenticated %}
<form action="{% url 'favorite' object.id %}" method="POST">
{% csrf_token %}
{% if user_is_favorite %}
<button type="submit" name="favorite" value="{{object.id}}">Not favorite</button>
{% else %}
<button type="submit" name="favorite" value="{{object.id}}">Favorite</button>
{% endif %}
</form>
{% else %}
Log in to add user to favorites.<br>
{% endif %}
urls.py
path('profile/<str:username>/', UserView.as_view(), name='profile-details'),
path('favorite/<str:username>/', FavoriteView, name="favorite"),
One immediate problem I see is that your URL path wants a string for the username, but your URL for the form gives it the ID of the user, so that'll be an int.
In terms of your error, you're trying to pass a username, but I don't think that'll be in the POST data. However if it was, you should be able to do;
get_object_or_404(User, username=request.POST.get('username'))
However, based on my initial comment, you should probably just get the user by ID like you are doing, but use the PK of the user which is comes with your view;
get_object_or_404(User, id=pk)
You may also come across more errors, because you're assigning an object, if it exists to Type1.favorite and then attempting to do Type1.favorite.filter( which will fail. You can only .filter() on a queryset, not a model instance.

Django: submit only one form created with for loop

In my code I'm using a class CreateView with a ListView. I'm also using a for loop to show all the possible dates available (that are created in the StaffDuty models). My user should be able to just book a single date.
My problem is that I'm not able to save a single appointment, I have to compile all the form showed in my list to be able to submit. How can I solve this?
models.py
class UserAppointment(models.Model):
user = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
staff = models.ForeignKey(StaffDuty, on_delete=models.CASCADE)
event = models.ForeignKey(Event, on_delete=models.CASCADE)
event_name = models.CharField(max_length=255)
date_appointment = models.DateField(null=True)
def __str__(self):
return self.event.name | str(self.staff.date_work)
def get_absolute_url(self):
return reverse('home')
views.py
class UserAppointmentAddView(CreateView):
model = UserAppointment
form_class = UserAppointmentForm
template_name = "reservation.html"
def form_valid(self, form):
form.instance.user = self.request.user.userinformation
def get_context_data(self, **kwargs):
kwargs['object_list'] = StaffDuty.objects.order_by('id')
return super(UserAppointmentAddView, self).get_context_data(**kwargs)
html
<div class="container">
<form method="post">
{% csrf_token %}
{% for appointment in object_list %}
<span>{{ form.staff }}</span>
<span>{{ form.event }}</span>
<span>{{ form.morning_hour }}</span>
<span>{{ form.afternoon_hour }}</span>
<div class="primary-btn">
<input type="submit" value="submit" class="btn btn-primary">
</div>
</div>
{% endfor %}
If I understand right, you are generating the same form for each appointment/ model instance, which won't work. What gets POSTed back does not identify which appointment object it refers to. That could be fixed with a hidden object_id field ... which is part of what model formsets do for you. Or you might put it into the value of your multiple submit buttons and pull it out of request.POST if you are happy to handle validation yourself.
The pure-Django solution is to generate a model formset. When it is submitted you would process the form that has changed and ignore the rest. (It will in fact give you lists of changed and unchanged objects).
The other approach would be using JavaScript to populate one form (possibly with hidden fields) when the user clicks on values in a table. Or no form in the template, just POST from JavaScript and validate what comes back through a form (or otherwise)
Formsets and model formsets look fearsomely complicated the first time you try to use them. They aren't really, but the first one you code may present you with a bit of a steep learning curve. It's easier if you can find something working that somebody else has already written, and adapt it.

Django custom template tags

Why my custom template tag doesn't work?
templatetags.py:
from django import template
from ..models import User
register = template.Library()
#register.inclusion_tag('main/post_detail.html', takes_context=True)
def get_user_liked_posts():
request = context['request']
user = User.objects.get(username=request.user.username)
liked_posts = []
for post in user.liked_posts.all():
liked_posts.append(post.name)
return {'liked_posts': liked_posts}
post_detail.html:
{% load static %}
{% load templatetags %}
<nav class="blog-pagination" aria-label="Pagination">
<span id="likes_count">{{ post.likes_count }}</span>
{% if post.name in liked_posts %}
<button id="like_button" class="btn btn-outline-primary btn-primary text-
white">Like</button>
{% else %}
<button id="like_button" class="btn btn-outline-primary">Like</button>
{% endif %}
</nav>
views.py:
class PostDetailView(DetailView):
model = Post
slug_field = 'url'
class LikePostView(View):
def post(self, request, slug):
post = Post.objects.get(id=request.POST['id'])
user = User.objects.get(username=request.user.username)
if request.POST['like'] == 'true':
post.likes_count += 1
user.liked_posts.add(post)
else:
post.likes_count -= 1
user.liked_posts.remove(post)
user.save()
post.save()
return redirect('post_detail', slug)
models.py:
class Post(models.Model):
"""
This is post model
"""
name = models.CharField(max_length=150, blank=False)
article = models.TextField(blank=False)
image = models.ImageField(upload_to='uploads/', blank=True)
likes_count = models.IntegerField(default=0)
url = models.CharField(max_length=150, blank=False)
def get_absolute_url(self):
return reverse('post_detail', kwargs={'slug': self.url})
I want to check if the post is in the liked post of the current user, but it doesn't work.
It doesn't show any errors, it just does nothing.
User in my app must like or unlike posts. In models, I have many to many relationship user with the post. I want to check if the user likes this post
The problem is that you don't even use the template tag, furthermore this is not even needed as you can simply write something like so in the template:
{% if post in request.user.liked_posts.all %}
A Liked post
{% else %}
Not a liked post
{% endif %}
But this is a bit inefficient as we are getting all the posts liked by the user just to check if they like some post. Also if this were in a loop with multiple posts we would be making a query for each post.
Instead we can simply annotate whether the user likes a post in the view itself using an Exists subquery [Django docs] on the through model of the many to many:
from django.db.models import Exists, OuterRef
class PostDetailView(DetailView):
model = Post
slug_field = 'url'
def get_queryset(self):
queryset = super().get_queryset()
queryset = queryset.annotate(
liked_by_user=Exists(
User.liked_posts.through.objects.filter(
post_id=OuterRef("pk"),
user_id=self.request.user.id
)
)
)
return queryset
Now in the template we can simply write:
{% if post.liked_by_user %}
A Liked post
{% else %}
Not a liked post
{% endif %}
Note: Your way of saving the count similarly can simply be turned into an annotation using the Count aggregation function [Django
docs].
Generally one should not store calculated attributes in a column since
that might lead to inconsistent data when updating and forgetting to update the related count, etc.

Creating a Dynamic Form in Django

i'm currently working on a project that would have a database of reports of a scam. In the report section of the website I have a form, but I want anyone to be able to add multiple profiles with a click of a button. For example:
Nickname field: xyz
Steam profile: x
[ + ] <- button for adding more profiles, which when pressed would look something like this:
Nickname field: xyz
Steam profile: x
Steam profile 2: y [ Delete ]
[ + ]
I was looking into FormSets and Inline Formsets, but nothing turned up that would match this specific need, aswell as did not answer the question regarding storing the results of the form.
How would I go about creating the form for this?
How would I store the multiple results of Steam profile to my object that has a steam_profile = models.CharField?
My current model:
class Scammer(models.Model):
#Basic information for display
steam_id_64 = models.CharField(max_length=17, default='00000000000000000')
nickname = models.CharField(max_length=64, default='Nickname')
steam_profile = models.CharField(max_length=512, default='https://www.steamcommunity.com')
description = models.TextField(default='')
proof = models.TextField(default='')
#Date created var for ordering
date_created = models.DateTimeField(default=timezone.now, blank=True)
def __str__(self):
return self.nickname
def get_absolute_url(self):
return reverse('dashboard-home')
My view:
class ScammerCreateView(CreateView):
model = Scammer_Unapproved
template_name='dashboard/report.html'
fields = ['nickname', 'steam_id_64', 'steam_profile', 'description', 'proof']
My template:
{% block content %}
<div class="report-scammer">
<form method="POST">
{% csrf_token %}
<fieldset>
<legend>Report a scammer</legend>
{{ form|crispy }}
</fieldset>
<div>
<button type="submit">Report</button>
</div>
</form>
</div>
{% endblock content %}
Have the profiles as the Builtin Django User model or maintain a separate model with a link to the inbuilt User model. Now the scam report form can have the option to be linked to Multiple User accounts by using ManytoMany relationship field. check the below official documentation page,
[https://docs.djangoproject.com/en/3.1/topics/db/examples/many_to_many/][1]

Django adding to database with foreign key while still showing the informations from the other model

I want to add elements to my database (for model Student) while having stuff from another model (School) be displayed alongside the form for the Student.\
This is the models.py
class School(models.Model):
name = models.CharField(max_length=256)
principal = models.CharField(max_length=256)
location = models.CharField(max_length=256)
def get_absolute_url(self):
return reverse('basiccbv:detail', kwargs={'pk':self.pk})
def __str__(self):
return self.name
class Student(models.Model):
name = models.CharField(max_length=256)
age = models.PositiveIntegerField(validators= [validators.MinValueValidator(1),validators.MaxValueValidator(20)],default=1)
school = models.ForeignKey(School, related_name='students')
def __str__(self):
return self.name
In my views.py I have this:
class SchoolDetailedView(DetailView):
context_object_name = 'school_detail'
model = models.School
template_name = 'basiccbv/school_detail.html'
# What i want is when I visit the link in the description I want to
# to see the school stuff and the form to add the student in this new
# view
class StudentCreateView(CreateView):
model = models.School
# I tried using the Student but that I don't know how to display the
# school information, I tried with related_name = 'students' but it
# didn't work(I don't know if that can be done the way that intended it
# or I just don't have the knowledge )
fields = ['name', 'age']
# If I use School I could get the name of the school in the title and
# its primary key, but than I don't know how to display the form and
# vise versa
template_name = 'basiccbv/student_update.html'
This is the .html file that gets me to the page where I need the form.
The link is the one calling 'basiccbv:studentupdate'
The related_name students was used here but I still can't figure out if it can
be done for adding stuff the way I want
<h1>Welcome to the school details page</h1>
<h2>School details:</h2>
<p>Name: {{ school_detail.name }}</p>
<p>Principal: {{ school_detail.principal }}</p>
<p>Location: {{ school_detail.location }}</p>
<h3>Students:</h3>
{% for student in school_detail.students.all %}
<p>{{ student.name }} who is {{ student.age }} years old.</p>
{% endfor %}
<div class="container">
<p><a href="{% url 'basiccbv:studentupdate' pk=school_detail.pk %}">Add a
student</a></p>
And here is the .html file with the form
## I changed this part bellow but nothing worked
<h1>Student coming to {{ student.school.name }}</h1>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" class="btn btn-primary" value="Add student">
</form>
I'm really stuck and can't find any information about this but if you can help me or give any advice thank you.
The way I used to add students was with admin and for schools I used admin until I created the view for creating Schools which worked with no problems(probably because there were no foreign keys).
I think you can take this approach
Forms:
# We need to define a new form first
class StudentForm(forms.ModelForm):
class Meta:
model = Student
fields = ['name', 'age']
Views:
# we are using form view for using the form mentioned above
class StudentCreateView(FormView):
form_class = StudentForm
success_url = "/"
def get(self, request, school_id, **kwargs):
context = self.get_context_data(**kwargs) # getting context, ie: the form
context[school] = School.objects.get(pk=school_id) # updating the context with school object using the PK provided with the url
return self.render_to_response(context)
def post(self, request, school_id, **kwargs):
# overriding default implementation
form = self.get_form()
if form.is_valid():
return self.form_valid(form, school_id) # passing the pk value to form valid function to override
else:
return self.form_invalid(form)
def form_valid(self, form, school_id):
# overriding default implementation
self.object = form.save(commit=False)
self.object.school = School.objects.get(id=school_id) # saving the school information to the object
self.object.save()
return super(StudentCreateView, self).form_valid(form)
Template
# template
<h1>Student coming to {{ school.name }}</h1>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" class="btn btn-primary" value="Add student">
</form>
Urls
path('school/student-update/<int:school_id>/', StudentCreateView.as_view(), name='studentupdate'),

Categories

Resources