Thanks in advance for any help. I am new to django specifically as well as web development in general. I have been trying to teach myself and develop a website using the Django framework and while everything is working so far, I am not sure if I am really doing things in the best possible way.
Typically, within my django app, I will have certain points where I want to modify the contents of my database model in some way. A typical use case is where I have button on my site that says "Add a post":
models.py:
from django.db import models
# data model import
from django.contrib.auth.models import User
# Create your models here.
class Post(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
post = models.CharField(max_length=1024)
urls.py:
from django.urls import include, path
from . import views
urlpatterns = [
path('', views.post, name='new_post'),
path('post_exec', views.post_exec, name='post_exec'),
]
views.py:
from django.contrib import messages
from django.shortcuts import render, redirect
# import data models
from django.contrib.auth.models import User
from .models import Post
def post(request):
# make sure the user has privileges
user = User.objects.get(id = request.user.id)
if not user.is_superuser:
return redirect('home')
return render(request, 'post.html')
def post_exec(request):
# make sure the user has priveledges
user = User.objects.get(id = request.user.id)
if not user.is_superuser:
return redirect('home')
# create the post
new_post = Post.objects.create()
new_post.user = user
new_post.post = request.POST['post']
new_post.save()
# this could redirect to a confirmation page or something too
return redirect('home')
You can see what I am doing here is that everytime I need a change to the database due to user input, I am executing the code to do the requested database change in a django view and then redirecting to another page following completion of the database change. While this works, it feels kind of clunky (you end up needing pages that you never actually use), it seems pretty sketchy in terms of security (although I am checking for credentials on each page), and I imagine there is a better way to do this.
Can any experience Django developers offer some advice for how I should be best structuring my project for these specific types of common database operations?
The Django's views is a good place to organize the project's CRUD system so users can manage their data. You can use the class-based views to group the GET, POST etc requests. Also there are better ways of using the authorization system with the login_required() decorator, the LoginRequiredMixin class and other solutions that you can rich here
Related
everyone. I hope you're doing well. I'm a Django newbie, trying to learn the basics of RESTful development. I only know Python, so this is my best fit for the moment.
Right now I'm trying to implement Unit tests for my API. It's a simple model to implement CRUD on the names and heights of NBA players. In my models I added a class to describe this data and translated it to a view with ModelViewSets. I wanted to make this data editable only for a specific type of user (a read-write user), only readable for another (read-only user) as well as unaccesible to non-authenticated users. To do so, I created a custom User Model and translated it to my views with a custom permission. Now I want to write a few Unit tests to check that:
r/w user can create a new player
r/w user can get a list of players
r/o user cannot create a new player
r/o user can get a list of players
unauthenticated user cannot create a new player
unauthenticated user cannot get a list of players
Here is my models.py:
from django.db import models
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
ROLES = [('read-only', 'read-only'), ('read-write', 'read-write'),]
role = models.CharField(max_length=32, choices=ROLES, default='read-only')
# Create your models here.
class NBAplayers(models.Model):
first_name = models.CharField(max_length=100)
h_in = models.DecimalField(max_digits=5, decimal_places=2)
h_meters = models.DecimalField(max_digits=5, decimal_places=2)
last_name = models.CharField(max_length=120)
def __str__(self):
return self.first_name
And my views.py:
from .models import NBAplayers, User
from .serializers import NBAplayersSerializer
from rest_framework.response import Response
from rest_framework import status, viewsets, permissions
class ReadOnlyPermission(permissions.BasePermission):
def has_permission(self, request, view):
requests = ('POST', 'PUT', 'DELETE', 'PATCH')
user = request.user
role = User.role
if user.is_anonymous: # Not Authenticated
return request.method == 'GET'
else:
if role == 'read-write':
return request.method in requests + ('GET',)
else: # Read Only User
return request.method == 'GET'
class NBAPlayersViewSet(viewsets.ModelViewSet):
serializer_class = NBAplayersSerializer
queryset = NBAplayers.objects.all()
permission_classes = [ReadOnlyPermission]
And Finally, my urls.py:
from django.contrib import admin
from django.urls import path, include
from .views import NBAPlayersViewSet
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register('players', NBAPlayersViewSet, basename = 'Players list')
urlpatterns = [
#djoser basic authentication
#Routers URLs
path('', include(router.urls)),
path('players/<int:pk>/', include(router.urls)),
path('', include('djoser.urls')),
path('', include('djoser.urls.authtoken')),
]
All the code above required to add the custom user to the global settings with AUTH_USER_MODEL = 'api_basic.User'. So, I've read the documentation and watched a few videos trying to understand how to write the proper tests, but the examples are not that close to this problem. I wanted to ask for a few pointers in the right direction, so that I can use it to build the rest of the tests. This is my first time writing unit tests.
Thank you beforehand for any help or input you can give. Cheers!
There's nothing tricky about this. For example, let's say I have a service that is only available to Admin/Staff users. It means you need to be both logged in and be an Admin.
In my test suite, I'll simply create a test_permissions method where I'll do something like:
Be logged out
Try the service, assert failure
Create normal user and log him in
Try the service, assert failure
Create admin user and log him in
Try the service, assert success
To give you a more concrete example, here's a snippet example with DRF. Do note that I'm using custom functions and 3rd party libraries to do some stuff, but the logic remains the same:
class TestCaseSuite(Base):
def test_permissions(self):
user = UserFactory()
admin = AdminFactory()
# 401 Not authenticated
self.api_client.logout()
response = self.api_client.post(url, data=data)
assert response.status_code == 401
# 403 Not admin
self.api_client.force_authenticate(user)
response = self.api_client.post(url, data=data)
assert response.status_code == 403
# 201 Admin
self.api_client.logout()
self.api_client.force_authenticate(admin)
response = self.api_client.post(url, data=data)
assert response.status_code == self.success_code
# ...
# other tests for that service like:
# testing custom fields
# testing a successful call and the response output
# testing side-effects, like when sending an email or whatnot
# etc
A few things to note:
In THIS test, I'm ONLY testing permissions. I'll use other tests for testing field validation, response output/format, etc.
If you find yourself needing a specific user for several tests, you could create it in the setUp method (which is triggered before EVERY test)
So, if you want to test specific permissions, you simply have to create the necessary objects/users and assert your expectations:
user with THIS data should fail
user with THAT data should succeed
etc.
I have added some URLs in my Django API for posting deleting and putting data and I don't know how to authenticate users first and give some of them to use these methods and ban some of them
As far as I know, you can use inbuilt decorator
#user_passes_test
then you can specify who can access your views just like below,
from django.contrib.auth.decorators import user_passes_test
def admin_user(user):
return user.is_superuser # write your logic here
#user_passes_test(admin_user)
def your_view(request):
--------
Have a look at the documentation for more clarification: https://docs.djangoproject.com/en/1.11/topics/auth/default/#django.contrib.auth.decorators.user_passes_test
Since you are using the tag django-rest-framework, I assume that your view is being created with Django REST Framework.
First, you should force users to be authenticated to use the API. Second, you need to define what types of permissions are needed to perform the actions.
You stated that Django Super Users should be able to perform these actions. Thus, you could create a custom permission to make sure that only a user that is a Django Super User will have permission:
from rest_framework.permissions import BasePermission
class IsSuperUser(BasePermission):
"""
Allows access only to admin users.
"""
def has_permission(self, request, view):
is_superuser = request.user and request.user.is_superuser
if not is_superuser and request.user:
# Your ban logic goes here
pass
return is_superuser
Then on your view, you can do:
from rest_framework.views import APIView
from your_app.permissions import IsSuperUser
class YourApiView(APIView):
permission_classes = [IsSuperUser]
If this is not enough information for you, I would suggest that you follow Django REST Framework's tutorial.
I'm using django and its built in admin site for the project I'm working on. (Python 3.4, django 1.7)
For the current project I'm working on I require usernames to be fixed after object creation. I have made a small test project, which literally contains one app, and that app only contains this admin.py:
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User
class MyAdmin(UserAdmin):
def get_readonly_fields(self,request, obj=None):
ro_fields = list(super().get_readonly_fields(request, obj))
if obj is not None:
ro_fields.append('username')
return ro_fields
admin.site.unregister(User)
admin.site.register(User, MyAdmin)
As expected, the Username can be set in the Add page and is Read Only in the change page. However, I cannot save when on the change page, I receive the message "please correct the errors below" but no field is highlighted. Can anyone suggest a way to work round this problem?
EDIT: as a diferent option I attempted to replace the username with a widget that didn't allow input - no luck there either
I created a very basic login application. The LOGIN_URL, LOGIN_REDIRECT_URL and the LOGOUT_URL work just fine.
The thing is that if i log in succesully and then i close the window (browser) and then i reopen the browser, i can perfectly search the url (the one i am supose to only see if i am logged in) and use the applcation without loggin in again. This is not where it ends: if i open 2 separate browsers: Chrome and Firefox at the same time and i only logged in in ONE, i can use the applicaction from the other browser without problems.
I dont want this to happend. I tried using login_required in the urls.conf for the index view (the page that you can only see if you are logged in) and in the class view, i tried using stronhold, and django-braces. Is ther any other way to do this?. If you want to see any of the code let me know. Im using the login and log out that django implements (auth).
Than you
I know this is an old post but maybe the answer will help others in the future.
if you workflow is class based views you have to use: LoginRequiredMixin
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views import generic
class CreateGroup(LoginRequiredMixin, generic.CreateView):
fields = ('name', 'description')
model = Group
The LoginRequired mixin
When using class-based views, you can archive the same behavior as with login_required by using the LoginRequiredMixin. This mixin should be at the leftmost position in the inheritance list.
https://docs.djangoproject.com/en/2.0/topics/auth/default/
It would seem like you need to wrap the 'private' views with Django's login_required decorator:
from django.contrib.auth.decorators import login_required
#login_required
def my_view(request):
...
This will redirect to LOGIN_URL if the user is not logged in or authenticated.
in the view to the which your login redirects you can add:
def after_login(request,..):
if not request.user.is_authenticated():
redirect('some-non-existence-page-or-404',...)
#Your code
return
I would like to implement a 2nd admin site which provides a subset of feature of the primary admin site. That's possible and described in the Django docs
However, I would like to limit access on the primary admin site. Some users can access the 2ndary site but not the primary site.
In order to implement that feature, I would like these users not to be in the staff (is_staff=False) and rewrite the AdminSite.has_permission
class SecondaryAdminSite(AdminSite):
def has_permission(self, request):
if request.user.is_anonymous:
try:
username = request.POST['username']
password = request.POST['password']
except KeyError:
return False
try:
user = User.objects.get(username = username)
if user.check_password(password):
return user.has_perm('app.change_onlythistable')
else:
return False
except User.DoesNotExist:
return False
else:
return request.user.has_perm('app.change_onlythistable')
Unfortunately, this approach doesn't work. The user can login but can't see anything in the secondary admin site.
What's wrong with this approach?
Any idea how to implement this feature?
Thanks in advance
Here's what worked for me with Django >= 3.2.
Create a subclass of AdminSite
Override the has_permission() method to remove the is_staff check.
Override the login_form to use AuthenticationForm.
AdminSite uses AdminAuthenticationForm, which extends AuthenticationForm and adds a check for is_staff.
Code
# PROJECT/APP/admin.py
from django.contrib.admin import AdminSite
from django.contrib.admin.forms import AuthenticationForm
class MyAdminSite(AdminSite):
"""
App-specific admin site implementation
"""
login_form = AuthenticationForm
site_header = 'Todomon'
def has_permission(self, request):
"""
Checks if the current user has access.
"""
return request.user.is_active
site = MyAdminSite(name='myadmin')
I think that your approach should now be possible: http://code.djangoproject.com/ticket/14434 (closed 5 weeks ago)
However, the explicit "is_staff" check is still done in two places (apart from the staff_member_required decorator):
django.contrib.admin.forms.AdminAuthenticationForm.clean()
On top of "has_permission()" you'd need to provide your non-staff AdminSite with a "login_form" that doesn't do the is_staff check, so could just subclass and adjust clean() accordingly.
templates/admin/base.html
would need to be slightly customized.
The div with id "user-tools" is only shown for active staff members. I'm assuming that's done because the login form also uses this template, and someone could be logged in as an active non-staff member but still should'nt see those links.
What's wrong with this approach? Any idea how to implement this feature?
What's wrong with this approach is that permissions and groups can already provide you with what you need. There is no need to subclass AdminSite if all you need is to divide users.
This is probably why this feature is so poorly documented, IMHO