Class-Based View of 'User' model auto-authenticates user - python

I've made a class-based view (DetailView) of app user's profile and for some reason anyone who visits the view is automatically considered authenticated even without entering any credentials. This happens without adding any extra logic in neither view nor template, just basic DetailView. The code is below:
views.py
from django.views.generic import DetailView
from django.contrib.auth.models import User
class ProfileDetail(DetailView):
model = User
template_name = 'index.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
return context
urls.py
from django.urls import path
from .views import ProfileDetail
urlpatterns = [
path('<int:pk>/', ProfileDetail.as_view())
]
template (index.html)
{{ user.is_authenticated }} {# returns True #}
{{ user }} {# returns the user with the corresponding id #}
The question is why does Django do it and is there any way to circumvent it except of using function-based view? I've looked through the docs, but couldn't find an answer.

To implement authentication in Django Class-Based Views, I've used LoginRequiredMixin, as it's explained here:
https://docs.djangoproject.com/es/4.0/topics/auth/default/
Code (from Django site):
from django.contrib.auth.mixins import LoginRequiredMixin
class MyView(LoginRequiredMixin, View):
login_url = '/login/'
redirect_field_name = 'redirect_to'
Another way is to pass the view that we want to authenticate to the login_required function, in the mapping of urls.py:
from django.contrib.auth.decorators import login_required
path('<int:pk>/', login_required(ProfileDetail.as_view())) #not tested

The simplest way to make any page login_required in class based views is to use method_decoratordjango-doc
In your ProfileDetail you can implement in the following way:
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
method_decorator(login_required(login_url="/any_login_route/"),name='dispatch')
class ProfileDetail(DetailView):
...
...
It will make the page login_required,and set login_url so that it can redirect to login page if user is not authenticated, for more information you can see docs by clicking on above method.

Related

Django: How to submit the current user's username through a form without the user having to enter it

I'm looking for a simple way to have a django form submit the currently logged-in user's username behind the scenes when the user submits a form. I'd like this to happen automatically.
I've seen examples in other stackoverflow entries where the init function is called. However, I find this way to be very messy and verbose.
I'm using Django 2.2.
I found a good, standard way to do this.
The best way is to use the generic class-based view called CreateView. Here is a clear explanation in the Django docs:
https://docs.djangoproject.com/en/2.2/topics/class-based-views/generic-editing/#models-and-request-user
I'll use the example from the docs in this explanation.
First: Use a basic model for the form. Nothing special here. (models.py)
from django.contrib.auth.models import User
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=200)
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
# ...
Second: Create a class-based view. In that class-based view, modify "def form_valid(self, form)" as shown in the docs example below. (views.py)
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.edit import CreateView
from myapp.models import Author
class AuthorCreate(LoginRequiredMixin, CreateView):
model = Author
fields = ['name']
def form_valid(self, form):
form.instance.created_by = self.request.user
return super().form_valid(form)
Third: Define urls. Add the regular "as_view()" as per all class-based views. (urls.py)
app_name = 'logs'
urlpatterns = [
path('', views.AuthorCreate.as_view(), name = "author_create"),
]
Fourth: The CreateView generic class-based view has a default template name linked to it (see django docs for CreateView, under the "template_name" attribute). The default template name follows the convention of "authorcreate_form.html". So create a html template with filename following that convention in the app's templates folder. The following template worked for me. (.html)
<form method="POST" action="">
{% csrf_token %}
{{ form.as_p }}
<input type="submit">
</form>
Again, this is the basic example that's provided in the Django docs to pass a user's username through a user form. This worked well for me.

Django, Can't get delete button to work

I have a section of my site where an admin can add a widget. However the delete button to delete any current widgets is not working. I have this code implemented else where on my site and it is working.
I copied the same code from the other section of my site where this is being used. The other section of my site which uses this code is a little different in that a post can only be deleted be the user who created it, and the widgets can be delete by any user with the "access_level" field is equal to "admin". However, I am the only admin so it should still work. The page that the widget stuff is displayed on is only accessible if your "access_level" is equal to admin so I don't need to validate whether or not they have the permission before deleting. Please help.
widget_list.html:
{% extends "base.html" %}
{% block content %}
<div class="container">
<div class="content">
<div class="widgets-list">
{% for widget in widget_list %}
<h3>{{ widget.name }}</h3>
<h3>{{ widget.widget_order }}</h3>
<div>
<p>{{ widget.body }}</p>
</div>
{% if user.is_authenticated %}
<a class="auth-user-options" href="{% url 'adminpanel:delete-widget' pk=widget.pk %}">Delete</a>
{% endif %}
{% endfor %}
</div>
</div>
</div>
{% endblock %}
Adminpanel app views.py:
from django.shortcuts import render
from adminpanel.forms import WidgetForm
from adminpanel.models import Widget
from django.utils import timezone
from django.contrib.auth import authenticate,login,logout
from django.http import HttpResponseRedirect, HttpResponse
from django.core.urlresolvers import reverse,reverse_lazy
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin
from braces.views import SelectRelatedMixin
from django.views.generic import (TemplateView,ListView,
DetailView,CreateView,
UpdateView,DeleteView)
# Create your views here.
class CreateWidgetView(LoginRequiredMixin,CreateView):
login_url = '/login/'
redirect_field_name = 'index.html'
form_class = WidgetForm
model = Widget
def form_valid(self,form):
self.object = form.save(commit=False)
self.object.save()
return super().form_valid(form)
def get_success_url(self):
return reverse('adminpanel:widgets')
class SettingsListView(ListView):
model = Widget
ordering = ['widget_order']
class DeleteWidget(LoginRequiredMixin,SelectRelatedMixin,DeleteView):
model = Widget
select_related = ('Widget',)
success_url = reverse_lazy('adminpanel:widget')
def get_queryset(self):
queryset = super().get_queryset()
return queryset.filter(user_id=self.request.user.id)
def delete(self,*args,**kwargs):
return super().delete(*args,**kwargs)
Project url spy:
from django.conf.urls import url
from django.contrib import admin
from django.conf.urls import include
from accounts import views
from colorsets import views
from colors import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^$',views.home,name='index'),
url(r'^accounts/',include('accounts.urls',namespace='accounts')),
url(r'^colorsets/',include('colorsets.urls',namespace='colorsets')),
url(r'^adminpanel/',include('adminpanel.urls',namespace='adminpanel')),
]
Adminpanel app urls.py:
from django.conf.urls import url
from adminpanel import views
app_name = 'adminpanel'
urlpatterns = [
url(r'^widgets/',views.SettingsListView.as_view(),name='widgets'),
url(r'^new/$',views.CreateWidgetView.as_view(),name='create-widget'),
url(r'^delete/(?P<pk>\d+)/$',views.DeleteWidget.as_view(),name='delete-widget'),
]
EDIT: Here is the error I'm getting I forgot to add it.
FieldError at /adminpanel/delete/10/
Cannot resolve keyword 'user_id' into field. Choices are: body, id, name, widget_order
and the traceback points to this:
/Users/garrettlove/Desktop/colors/adminpanel/views.py in get_queryset
return queryset.filter(user_id=self.request.user.id) ...
▶ Local vars
Adminpanel app models.py (widget model):
from django.db import models
from adminpanel.choices import *
# Create your models here.
class Widget(models.Model):
name = models.CharField(max_length=50)
widget_order = models.IntegerField(blank=False,unique=True)
display_name = models.IntegerField(choices=WIDGET_NAME_CHOICES,default=1)
body = models.TextField(max_length=500)
def __str__(self):
return self.name
As your error is saying:
Cannot resolve keyword 'user_id' into field.
And it points to this line:
return queryset.filter(user_id=self.request.user.id)
It's that your queryset model does not have a user_id field. In your DeleteWidget view you have specified model = Widget
and as you see in your Widget model, you have the following fields: body, id, name, widget_order. There is no user_id field.
You need to change your filtering logic. If you want to have a related User model, you should use ForeignKey relation and then you can filter by the User's id.
In your models.py:
from django.contrib.auth.models import User
class Widget(models.Model):
# ...
user = models.ForeignKey(User, on_delete=models.CASCADE)
And then in your DeleteWidget's get_queryset, you can do the following:
return queryset.filter(user__id=self.request.user.id)
# __________________________^
# Note that there is a double underscore here!
# This is because Django uses double underscore for accessing related models in queries.

How do create a link from a database in Django?

If you have a blog model in Django that saves a slug, how do you create an href link from that slug field?
You should create a view according to Django MVT architecture.
For example you can create a DetailView and associate it with an URL.
# views.py
from django.views.generic.detail import DetailView
from someapp.models import SomeModel
class SomeModelDetailView(DetailView):
model = SomeModel
# urls.py
from django.conf.urls import url
from someapp.views import SomeModelDetailView
urlpatterns = [
url(r'^(?P<slug>[-\w]+)/$', SomeModelDetailView.as_view(), name='somemodel-detail'),
]
And just use the {% url %} template tag to access to this page. For example :
{{ foo.name }}
Where foo is a SomeModel instance.

How to properly address a modified Django login form in the template files using crispy forms?

I have a modified Django login form and I have to address it in the template file using the long model like this {% crispy formname formname.helper %}. I can't use the short version ({% crispy form %}) because I have to differentiate between multiple forms. The thing is, all works well for normal forms, but not for a modified Django login form.
The code goes like this:
forms.py
from crispy_forms.helper import FormHelper
from django.contrib.auth.forms import AuthenticationForm
class LoginForm(AuthenticationForm):
def __init__(self, *args, **kwargs):
super(LoginForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_class = 'login-form'
self.helper.form_show_labels = False
self.helper.layout = Layout(
Field('username', placeholder="E-mail"),
Field('password', placeholder="Password")
)
self.helper.add_input(Submit('submit', 'Log in', css_class='btn-block btn-inset'))
views.py
from django.contrib.auth import authenticate, login as auth_login, REDIRECT_FIELD_NAME
from django.contrib.auth.views import login as django_login
from accounts.forms import LoginForm
from django.http import HttpResponseRedirect
def login(request):
if request.user.is_authenticated():
return HttpResponseRedirect('/profiles/create/')
else:
response = django_login(request, template_name='accounts/login.html', authentication_form=LoginForm)
return response
When I try to address it in the template in the form of {% crispy response response.helper %} I only get an error stating VariableDoesNotExist at /accounts/whateverurl/: Failed lookup for key [response].
How should I address it?
Django 1.6
EDIT:
The solution works when I want to call the login form from that particular view, but when I try to call it from profiles/views.py, not so much.
The profiles/views.py looks like this:
from django.contrib.auth import authenticate, login as auth_login, REDIRECT_FIELD_NAME
from django.contrib.auth.views import login as django_login
from django.views.generic import DetailView
from accounts.forms import LoginForm, RegisterForm
from accounts.views import get_redirect_url
class ProfileView(DetailView):
model = Profile
def get(self, request, *args, **kwargs):
#lots of irrelevant code#
if request.user.is_authenticated():
pass
else:
login_form = django_login(request, template_name='accounts/login.html', authentication_form=LoginForm).render()
#lots of irrelevant code#
context.update({
#lots of irrelevant code#
'login_form': login_form,
})
Do I even need to update context for login_form? Anyways, using it like this I get the same VariableDoesNotExist at /profiles/whateverurl/: Failed lookup for key [form].
When I replace {% crispy form form.helper %} with {% crispy login_form login_form.helper %} I get VariableDoesNotExist at /profiles/whateverurl/: Failed lookup for key [helper] instead.
I also tried to clone the working view into profiles/views.py and it does work, but only independently. If I include the new login view's template into ProfileView's template, it returns the error shown above.
Django's login view includes the login form in the template context as form. Therefore you should use:
{% crispy form form.helper %}
response = django_login(request, template_name='accounts/login.html', authentication_form=LoginForm)
is a view, and the form variable in its context is named form:
context = {
'form': form,
...
}
Thus you have to use
{% crispy form form.helper %}

Trying to query a database by current user, but Django stops at template if statement

This is my first post on Stack Overflow, so I apologize in advance if I am somehow not abiding by the question asking etiquette.
Currently trying to query the database represented by this model,
Models.py
from django.conf import settings
from django.db import models
from django.contrib.auth.models import User
class Things(models.Model):
posted_by = models.ForeignKey(settings.AUTH_USER_MODEL)
thing1 = models.CharField(max_length=30)
thing2 = models.CharField(max_length=30)
thing3 = models.CharField(max_length=30)
by the current user in this view,
Views.py
from django.contrib.auth.decorators import login_required
from django.shortcuts import render
from django.contrib.auth.models import User
from app.models import Things
#login_required(login_url='/login/')
def things(request):
user = request.user
results = Things.objects.filter(posted_by__exact=user)
return render(request, 'my_things.html', {'results' : results})
corresponding to this url,
Urls.py
from django.conf.urls import patterns, include, url
from app import views
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
url(r'^profile/things/$',views.things),
)
and this template:
my_things.html
{% if results %}
{% for thing in results %}
<h1>{{thing.thing1}} and {{thing.thing2}} and {{thing.thing2}}</h1>
{% endfor %}
{% else %}
<h1>You have no things!</h1>
{% endif %}
The error Django gives me when I try to load the page is an OperationalError saying there is "no such table: app_Things".
After some discussion in the comments, the diagnosis: traditional bugginess with a custom AUTH_USER_MODEL.
Check this: docs especially noting in the first warning the issues you may encounter regarding your database schema. If you're not using a custom user model but are instead just wanting to foreignkey to the built-in User object, you need to from django.contrib.auth.models import User and tie your relations to the User model rather than the thing referenced in settings.
I think you forgot to add your app name in INSTALLED_APPS in settings file and syncdb after that. Also this __exact query is not needed.

Categories

Resources