How do I can implement Follow/Unfollow system with CBV in Django? - django

I did a lot of attempts to create a following system. Who can describe in details how to implement this? My code shows a lot of errors. In AddFollower() and Remove follower() I get a profile I'm on and a current profile (my) and add or remove in database. But these functions don't work. Help me to find a solution, please
urls.py
urlpatterns = [
path('', PostListView.as_view(), name='blog-home'),
path('user/<str:username>/', GetUserProfile.as_view(), name='user-profile'),
path('post/<int:pk>/', PostDetailView.as_view(), name='post-detail'),
path('post/new/', PostCreateView.as_view(), name='post-create'),
path('post/<int:pk>/update/', PostUpdateView.as_view(), name='post-update'),
path('post/<int:pk>/delete/', PostDeleteView.as_view(), name='post-delete'),
path('about/', views.about, name='blog-about'),
path('post-like/<int:id>/', views.post_like, name='post-like'),
path('read-later/<int:id>/', views.read_later, name='read-later'),
path('read-later-list/<str:username>/', ReadLaterListView.as_view(), name='read-later-list'),
path('add-follower/<str:username>/', AddFollower.as_view(), name='add_follower'),
path('remove-follower/<str:username>/', RemoveFollower.as_view(), name='remove_follower')
views.py
class AddFollower(LoginRequiredMixin, View):
def post(self, request, *args, **kwargs):
user = get_object_or_404(User, username=self.kwargs.get('username'))
profile = Profile.objects.get(user=user)
profile.subscribers.add(request.user)
return redirect('user-profile', username=profile.user.username)
class RemoveFollower(LoginRequiredMixin, View):
def post(self, request, *args, **kwargs):
user = get_object_or_404(User, username=self.kwargs.get('username'))
profile = Profile.objects.get(user=user)
profile.subscribers.remove(request.user)
return redirect('user-profile', username=profile.user.username)
user_posts.html
{% if is_following %}
<form action="{% url 'remove_follower' user.username %}" method="POST">
{% csrf_token %}
<div class="buttons mt-3">
<button type="submit" class="btn btn-dark px-4 ms-3">Unfollow</button>
</div>
</form>
{% else %}
<form method="POST" action="{% url 'add_follower' user.username %}">
{% csrf_token %}
<div class="buttons mt-3">
<button type="submit" class="btn btn-dark px-4 ms-3">Follow</button>
</div>
</form>
{% endif %}

You do not give list of errors. Complicated for finding a solution of your problem.
But, i see some errors in your code.
in the post function, you access to user by user = self.kwargs.get('user'), but in url, you have pass only an id param, so you can retrieve your user with this line instead of previous:
from django.shortcuts import get_object_or_404
class AddFollower(LoginRequiredMixin, View):
def post(self, request, *args, **kwargs):
user = get_object_or_404(User, pk=kwargs.get('id'))
profile = Profile.objects.get(user=user)
profile.subscribers.add(request.user)
return redirect('user-posts', id=profile.id)
class RemoveFollower(LoginRequiredMixin, View):
def post(self, request, *args, **kwargs):
user = get_object_or_404(User, pk=kwargs.get('id'))
profile = Profile.objects.get(user=user)
profile.subscribers.remove(request.user)
return redirect('user-posts', id=profile.id)
I don't know if it the last errors. Please give us more details and message of errors.

So, finally I solved it. The problem was that I passed the wrong username in view. I got the logged in user's profile instead of current user, so I fixed it by adding a context data:
user = self.kwargs.get('username')
profile_user = User.objects.get(user=user)
And adding it to the template:
<form method="POST" action="{% url 'add_follower' profile_user.username %}">
{% csrf_token %}
<div class="buttons mt-3">
<button type="submit" class="btn btn-dark px-4 ms-3">Follow</button>
</div>
</form>

Related

PasswordResetConfirmView's PASSWORD_RESET_TIMEOUT Message if expired

Good day SO.
How to add message if PASSWORD_RESET_TIMEOUT is expired? I am using the django.contrib.auth PasswordResetView to get url link with token for Password Reset. Based on docs, I can add PASSWORD_RESET_TIMEOUT like this:
PASSWORD_RESET_TIMEOUT = 10
10 seconds just to test.
After 10 seconds pass, I tried to to refresh the page and user can still use the URL to access the PasswordResetConfirmView but can no longer change the password. Even using mismatched password, no response. How should I proceed with this?
For reference, this is my URL:
path('reset_password/', views.MyPasswordResetView.as_view(
template_name="../templates/logauth/reset_password.html",
subject_template_name='../templates/logauth/password_reset_subject.txt',
email_template_name='../templates/logauth/password_reset_email.html',
html_email_template_name='../templates/logauth/password_reset_email.html',
from_email=settings.EMAIL_HOST_USER,),
name="reset_password"),
path('reset_password_sent/', auth_views.PasswordResetDoneView.as_view(template_name="../templates/logauth/reset_password_sent.html"), name="password_reset_done"),
path('reset/<uidb64>/<token>/', auth_views.PasswordResetConfirmView.as_view(template_name="../templates/logauth/reset_password_2.html"), name="password_reset_confirm"),
path('reset_password_complete/', auth_views.PasswordResetCompleteView.as_view(template_name="../templates/logauth/reset_password_complete.html"), name="password_reset_complete"),
Form to alter my PasswordResetView:
class MyPasswordResetForm(PasswordResetForm):
username = forms.CharField(max_length=254)
field_order = ['username', 'email']
def __init__(self, *args, **kwargs):
super(MyPasswordResetForm, self).__init__(*args, **kwargs)
for field in self.fields:
self.fields[field].widget.attrs = {'class': 'user-input-form'}
View:
class MyPasswordResetView(PasswordResetView):
form_class = MyPasswordResetForm
def form_valid(self, form):
username = form.cleaned_data.get('username')
email = form.cleaned_data.get('email', '').lower()
try:
user = get_user_model().objects.get(username=username, email=email)
except(get_user_model().DoesNotExist):
user = None
if user is None:
return redirect('password_reset_done')
return super().form_valid(form)
Django provides in PasswordResetConfirmView class, a template context: validlink --> It is a Boolean, which returns True if the link (combination of uidb64 and token) is valid or unused yet.
Docs: https://docs.djangoproject.com/en/3.2/topics/auth/default/#django.contrib.auth.views.PasswordResetConfirmView
You can use this to do the logic inside the password_reset_confirm.html template like this:
{% if validlink %}
<div class="content-section">
<form method="POST">
{% csrf_token %}
<fieldset class="form-group">
<legend class="border-bottom mb-4">Change Password</legend>
{{ form }}
</fieldset>
<div class="form-group">
<button class="btn btn-outline-info" type="submit">Change password</button>
</div>
</form>
</div>
{% else %}
<div class="alert alert-info mt-2">
The link you follewd has expired! To login or reset password again click
here
</div>
{% endif %}

Unable to redirect Django form

I am trying to build a simple blog with a basic post function through a very simple form. The function I am trying to accomplish is:
if the new post POSTs (no errors etc.) save to the database and redirect to the newly created post_details page. Otherwise I want it to re render the post form again. I keep getting the error NoReverseMatch at /post/pk and I cannot find my error. I am obviously not understanding something properly. The related code is below:
views.py
def post_new(request):
if request.method == "POST":
form = PostForm(request.POST)
if form.is_valid():
post = form.save(commit=False)
post.author = request.user
post.published_date = timezone.now()
post.save()
return redirect('post_detail', pk=post.pk)
else:
form = PostForm()
return render(request, 'blog/post_edit.html', {'form': form})
post_edit.html
{% extends 'blog/base.html' %}
{% block content %}
<h2>New post</h2>
<form method="POST" class="post-form">{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="save btn btn-default">Save</button>
</form>
{% endblock %}
urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.post_list, name='post_list'),
path('post/<int:pk>/', views.post_detail, name='post_detail'),
path('post/new/', views.post_new, name='post_new'),
]
I found my error inside my link for in my template view post_detail.html:
<a class="btn btn-default" href="{% url 'post_edit' pk=post.pk %}"><span class="glyphicon glyphicon-pencil"></span></a>
It was redirecting properly to post_detail.html but the link above was breaking my page.

django-otp implementation using custom Views

I have been trying to implement the django-otp with qrcode using custom Forms and Views. The problem is I am a little caught up on whether my implementation is correct or not. As the documentation states that a request.user.is_verified() attribute is added to users who have been OTP verified, I am actually not able to get it right. Have created confirmed TOTP device for user with the QR code setup using Microsoft Authenticator app.
I was able to successfully implement the default Admin Site OTP verification without any issues. Below is the files for the custom implementation.
urls.py
from django.conf.urls import url
from account.views import AccountLoginView, AccountHomeView, AccountLogoutView
urlpatterns = [
url(r'^login/$', AccountLoginView.as_view(), name='account-login',),
url(r'^home/$', AccountHomeView.as_view(), name='account-home',),
url(r'^logout/$', AccountLogoutView.as_view(), name='account-logout',)
]
views.py
from django.contrib.auth import authenticate, login as auth_login
from django.views.generic.base import TemplateView
from django.views.generic.edit import FormView
from django_otp.forms import OTPAuthenticationForm
class AccountLoginView(FormView):
template_name = 'login.html'
form_class = OTPAuthenticationForm
success_url = '/account/home/'
def form_invalid(self, form):
return super().form_invalid(form)
def form_valid(self, form):
# self.request.user returns AnonymousUser
# self.request.user.is_authenticated returns False
# self.request.user.is_verified() returns False
username = form.cleaned_data.get('username')
password = form.cleaned_data.get('password')
otp_token = form.cleaned_data.get('otp_token')
otp_device = form.cleaned_data.get('otp_device')
user = authenticate(request=self.request, username=username, password=password)
if user is not None:
device_match = match_token(user=user, token=otp_token)
# device_match returns None
auth_login(self.request, user)
# self.request.user returns admin#mywebsite.com
# self.request.user.authenticated returns True
# self.request.user.is_verified returns AttributeError 'User' object has no attribute 'is_verified'
# user.is_verified returns AttributeError 'User' object has no attribute 'is_verified'
return super().form_valid(form)
class AccountHomeView(TemplateView):
template_name = 'account.html'
def get(self, request, *args, **kwargs):
# request.user.is_authenticated returns True
# request.user.is_verified() returns False
return super(AccountHomeView, self).get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['data'] = 'This is secured text'
return context
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Login</title>
</head>
<body>
<form action="." method="post">
{% csrf_token %}
{{ form.non_field_errors }}
<div class="fieldWrapper">
{{ form.username.errors }}
<label for="{{ form.username.id_for_label }}">{{ form.username.label_tag }}</label>
{{ form.username }}
</div>
<div class="fieldWrapper">
{{ form.password.errors }}
<label for="{{ form.password.id_for_label }}">{{ form.password.label_tag }}</label>
{{ form.password }}
</div>
{% if form.get_user %}
<div class="fieldWrapper">
{{ form.otp_device.errors }}
<label for="{{ form.otp_device.id_for_label }}">{{ form.otp_device.label_tag }}</label>
{{ form.otp_device }}
</div>
{% endif %}
<div class="fieldWrapper">
{{ form.otp_token.errors }}
<label for="{{ form.otp_token.id_for_label }}">{{ form.otp_token.label_tag }}</label>
{{ form.otp_token }}
</div>
<input type="submit" value="Log In" />
{% if form.get_user %}
<input type="submit" name="otp_challenge" value="Get Challenge" />
{% endif %}
</form>
</body>
</html>
Could anyone please let me know what is that I am missing. I want to be able to authenticate and verify them using my own views by using the existing OTP form classes.
Please advice.
What exactly is match_token? You don't need a device field, rather you try and a device for a user.
Here's my implementation for that.
from django_otp import devices_for_user
from django_otp.plugins.otp_totp.models import TOTPDevice
def get_user_totp_device(user, confirmed=None):
devices = devices_for_user(user, confirmed=confirmed)
for device in devices:
if isinstance(device, TOTPDevice):
return device
def create_device_topt_for_user(user):
device = get_user_totp_device(user)
if not device:
device = user.totpdevice_set.create(confirmed=False)
return device.config_url
def validate_user_otp(user, data):
device = get_user_totp_device(user)
serializer = otp_serializers.TokenSerializer(data=data)
if not serializer.is_valid():
return dict(data='Invalid data', status=status.HTTP_400_BAD_REQUEST)
elif device is None:
return dict(data='No device registered.', status=status.HTTP_400_BAD_REQUEST)
elif device.verify_token(serializer.data.get('token')):
if not device.confirmed:
device.confirmed = True
device.save()
return dict(data='Successfully confirmed and saved device..', status=status.HTTP_201_CREATED)
else:
return dict(data="OTP code has been verified.", status=status.HTTP_200_OK)
else:
return dict(
data=
dict(
statusText='The code you entered is invalid',
status=status.HTTP_400_BAD_REQUEST
),
status=status.HTTP_400_BAD_REQUEST
)
And then in a view, you can just do something like
create_device_topt_for_user(user=request.user)
validate_user_otp(request.user, request.data)

Django 2 make comment filed error

recently I decide to add a comment block to my template in my django app but when I add it to my app , I faced to this error :
add_comment_to_post() got an unexpected keyword argument 'item_id'
my template.html:
{% block content %}
<form action="#" method="post" novalidate="novalidate">
{% csrf_token %}
{{ form.as_p }}
<div class="row">
<div class="col-md-4">
<p><label>Name*</label><input type="text" name="your-name" value=""
size="60" class=""
aria-required="true"
aria-invalid="false"></p>
</div>
<div class="col-md-4">
<p><label>Email*</label><input type="text" name="your-email"
value=""
size="60" class=""
aria-required="true"
aria-invalid="false"></p>
</div>
<div class="col-md-4">
<p><label>Website</label><input type="text" name="your-website"
value=""
size="60" class=""
aria-required="true"
aria-invalid="false"></p>
</div>
<div class="col-md-12">
<p><label>Message</label><textarea name="your-message" cols="60"
rows="3" class=""
aria-invalid="false"></textarea>
</p>
</div>
</div>
<div class="dividewhite2"></div>
<p>
<button type="button" href="{% url 'add_comment_to_post' pk=item.pk %}"
class="btn btn-lg btn-darker">Post Comment
</button>
</p>
</form>
{% endblock %}
my models.py :
from django.db import models
from datetime import date
from django.utils import timezone
# Create your models here.
class Blogs(models.Model):
main_image = models.ImageField(upload_to='Blogs/', help_text='This Image Is Gonna To Be Show At Blogs Page.',
blank=False, default='')
class Comment(models.Model):
post = models.ForeignKey('Blog.Blogs', on_delete=models.CASCADE, related_name='comments')
author = models.CharField(max_length=200)
text = models.TextField()
created_date = models.DateTimeField(default=timezone.now)
approved_comment = models.BooleanField(default=False)
def approve(self):
self.approved_comment = True
self.save()
def __str__(self):
return self.text
my form.py:
from django.forms import ModelForm
from .models import Blogs, Comment
class CommentForm(ModelForm):
class Meta:
model = Comment
fields = ('author', 'text',)
my views.py :
from django.shortcuts import render, get_object_or_404, redirect
from Blog.forms import CommentForm
from .models import Blogs, Comment
def item(request, items_id):
items = get_object_or_404(Blogs, pk=items_id)
return render(request, 'Blog/Items.html', {'item': items, 'comments': Comment})
def add_comment_to_post(request, pk):
post = get_object_or_404(Blogs, pk=pk)
if request.method == "POST":
form = CommentForm(request.POST)
if form.is_valid():
comment = form.save(commit=False)
comment.post = post
comment.save()
return redirect('post_detail', pk=post.pk)
else:
form = CommentForm()
return render(request, 'blog/Items.html', {'form': form})
and my urls.py:
from django.urls import path
from Blog import views
from Blog import models
urlpatterns = [
path('<int:item_id>/', views.add_comment_to_post, name='add_comment_to_post'),
path('<int:items_id>/', views.item, name='Itemz'),
]
I checked my code many times but I can't understand what is my problem.
is any body know to how can I add comment to my app or what is my problem?
In addition, I'm sorry for writing mistakes in my question.
change this
def add_comment_to_post(request, pk):
To:
def add_comment_to_post(request, item_id):
Then change everywhere inside the function you wrote pk to item_id
views.py
def add_comment_to_post(request, item_id):
post = get_object_or_404(Blogs, pk=item_id)
if request.method == "POST":
form = CommentForm(request.POST)
if form.is_valid():
comment = form.save(commit=False)
comment.post = post
comment.save()
return redirect('post_detail', pk=post.pk)
else:
form = CommentForm()
return render(request, 'blog/Items.html', {'form': form, 'item': post})
and in your template:
<button type="button" href="{% url 'add_comment_to_post' item.pk %}"
class="btn btn-lg btn-darker">Post Comment
</button>
Double check your url patterns maybe? Try:
urlpatterns = [
path('<int:pk>/', views.add_comment_to_post, name='add_comment_to_post'),
The variable name in your view method needs to match the variable name in the url. Therefore you need both to be pk or both to be item_id
The Problem happen because in urls.py there were two subject that was pass to one views.
change views to this :
urlpatterns = [
path('<int:pk>/', views.item, name='Itemz'),
]
then change html part to this :
{% if not user.is_authenticated %}
<p><a href="{% url 'login' %}" class="btn btn-gr btn-xs">Login To Send
A Command.</a></p>
{% endif %}
<div class="dividewhite2"></div>
{% if user.is_authenticated %}
<form method="post" novalidate="novalidate">
{% csrf_token %}
{{ form.as_p }}
<div class="dividewhite2"></div>
<p>
<button href="{% url 'Itemz' item.id %}" type="submit"
class="btn btn-lg btn-darker">Post Comment
</button>
</p>
</form>
{% endif %}
now the Django project will run properly.

django-registration-redux custom user model login only working via /admin/

I am using a custom user model for my Django project and I can log in via /admin/ perfectly fine. But when I go to /accounts/login and try to log in, it just bounces me back to the login page without logging in. I am using django-registration-redux with the simple backend.
Via logging I discovered that the error happens in this method in django.contrib.auth.__init__.py:
def get_user(request):
"""
Returns the user model instance associated with the given request session.
If no user is retrieved an instance of `AnonymousUser` is returned.
"""
from .models import AnonymousUser
user = None
try:
#
# EXCEPTION THROWN ON BELOW LINE
#
user_id = _get_user_session_key(request)
backend_path = request.session[BACKEND_SESSION_KEY]
except KeyError:
pass
else:
if backend_path in settings.AUTHENTICATION_BACKENDS:
backend = load_backend(backend_path)
user = backend.get_user(user_id)
# Verify the session
if hasattr(user, 'get_session_auth_hash'):
session_hash = request.session.get(HASH_SESSION_KEY)
session_hash_verified = session_hash and constant_time_compare(
session_hash,
user.get_session_auth_hash()
)
if not session_hash_verified:
request.session.flush()
user = None
return user or AnonymousUser()
Any ideas? /accounts/register/ performs as expected, although I have overridden RegistrationView. Perhaps I have to do the same thing for logging in?
Login.html
{% extends "base.html" %}
{% load staticfiles %}
{% block body_block %}
<link href="{% static 'css/signin.css' %}" rel="stylesheet">
<div class="container">
<div class="jumbotron">
<h1 class="display-3" align="center">Login</h1>
</div>
<form method="post" action=".">
{% csrf_token %}
<h2 class="form-signin-heading">Please sign in</h2>
<label for="inputEmail" class="sr-only">Username</label>
<input type="text" name="email" id="id+username" class="form-control" placeholder="Username" required autofocus>
<label for="inputPassword" class="sr-only">Password</label>
<input type="password" name="password" id="id_password" class="form-control" placeholder="Password" required>
<button class="btn btn-lg btn-primary btn-block" type="submit" value="Submit">Login</button>
</form>
Not a member?
Register
</div>
<p>
</p>
{% endblock %}
Urls.py
class MyRegistrationView(RegistrationView):
success_url = '/'
form_class = UserProfileRegistrationForm
def get(self, request, *args, **kwargs):
form = self.form_class(initial=self.initial)
return render(request, self.template_name, {'form': form})
def register(self, form):
logging.debug("THIS IS MY REGISTER")
new_user = form.save(commit=False)
new_user.set_password(form.cleaned_data['password1'])
new_user.save()
login(self.request, new_user)
logging.debug("Logged in")
signals.user_registered.send(sender=self.__class__,
user=new_user,
request=self.request)
logging.debug("After signals")
return new_user
urlpatterns = [
url(r'^', include('base.urls')),
url(r'^admin/', admin.site.urls),
url(r'^accounts/register/$', MyRegistrationView.as_view(), name="registration_register"),
url(r'^accounts/password/change/$', MyRegistrationView.as_view(), name="auth_password_change"),
url(r'^accounts/password/change/done/$', MyRegistrationView.as_view(), name="auth_password_changed"),
url(r'^accounts/', include('registration.backends.simple.urls')),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
EDIT:
I have a temporary fix of throwing a view into login in urls.py. Something tells me this is extremely dirty but it seems to work... for now. I'm open to better alternatives.
url(r'^accounts/login/$', my_view, name="login"),
def my_view(request):
if request.POST:
username = request.POST['email']
password = request.POST['password']
user = authenticate(username=username, password=password)
if user is not None:
login(request, user)
return render(request, 'index.html', {})
# Redirect to a success page.
else:
# Return an 'invalid login' error message.
pass
else:
return render(request, 'registration/login.html', {})
Try using {{ form }} in your login template, instead of rendering the fields manually. This can show whether the problem is in your template or elsewhere.
In this case, I think that the form fields should be username and password, not email and password as you have.
<input type="text" name="username" id="id_username" class="form-control" placeholder="Username" required autofocus>
<input type="password" name="password" id="id_password" class="form-control" placeholder="Password" required>