I have 2 models that I want to preset the value and ideally have it hidden from the Django admin when creating new record, this way the user don't amend this value. This are the created and modified by that are foreign keys to users.
I found this link https://pypi.org/project/django-currentuser/, that i thought it might do the job, however it reverted my django from the latest version to version 3, so I dont want to use it, and also, it doesnt work if i set either created or last modified but not both, if i set it in the 2 models i get 4 errors.
I am wondering if there is an easy way to set this default value?
from django.db import models
from email.policy import default
from django.contrib.auth.models import User
from django.utils import timezone
# from django.contrib import admin
# https://pypi.org/project/django-currentuser/
from django_currentuser.middleware import (get_current_user, get_current_authenticated_user)
from django_currentuser.db.models import CurrentUserField
class Company(models.Model):
modified_by = models.ForeignKey(User, related_name='company_modified_by', unique = False, on_delete=models.CASCADE)
created_by = CurrentUserField()
created_date = models.DateTimeField(auto_now_add=True)
modified_date = models.DateTimeField(auto_now_add=True)
name = models.CharField(max_length=150, unique = True)
class Meta:
verbose_name = "Company"
verbose_name_plural = "Companies"
def __str__(self):
return self.name
class UserProfile(models.Model):
modified_by = models.ForeignKey(User, related_name='user_profile_modified_by', unique = False, on_delete=models.CASCADE)#CurrentUserField(on_update=True)
created_by = CurrentUserField()
created_date = models.DateTimeField(auto_now_add=True)
modified_date = models.DateTimeField(auto_now_add=True)
I've learned that instance doesn't work as described in a previous stackoverflow interaction. I've done some tinkering figured out how to do my usual is_edit flag in the admin
This is what I've come up with. It requires changing the admin.py and adding a new form.
The values will still show up in that table in the admin page, which I assume is good, they're just hidden in the new+edit forms.
Note: I only did Company as I'm not 100% sure on how UserProfile works as the only two fields are supposed to be hidden ones, so what's to edit?
admin.py
from django.contrib import admin
# this \/ needs to change
from myapp.forms import CompanyForm
from myapp.models import Company
class CompanyAdmin(admin.ModelAdmin):
# columns to show in admin table
list_display = (
'name',
'created_by', 'created_date',
'modified_by', 'modified_date',
)
# custom form
form = CompanyAdminForm
# override default form_save to pass in the request object
# - need request.user inside the save method for `{x}_by = user`
def save_form(self, request, form, change):
return form.save(commit=False, request=request)
admin.site.register(Company, CompanyAdmin)
forms.py
from django import forms
from django.contrib import admin
# this \/ needs to change
from myapp.models import Company
class CompanyForm(forms.ModelForm):
class Meta:
model = Company
fields =['name']
exclude =['modified_by', 'created_by', 'created_date', 'modified_date']
def __init__(self, *args, **kwargs):
# We must determine if edit here, as 'instance' will always exist in the save method
self.is_edit = kwargs.get('instance') != None
super(CompanyForm, self).__init__(*args, **kwargs)
def save(self, commit=True, *args, **kwargs):
# We must Pop Request out here, as super() doesn't like extra kwargs / will crash
self.request = kwargs.pop('request') if 'request' in kwargs else None
obj = super(CompanyForm, self).save(commit=False, *args, **kwargs)
# do your stuff!
from datetime import datetime
if self.is_edit:
obj.modified_date = datetime.now().date()
obj.modified_by = self.request.user
else:
obj.created_date = datetime.now().date()
obj.created_by = self.request.user
if commit:
obj.save()
return obj
Note: But you can reuse the Company form for a non-admin form, you just have to remember to call the save like: form.save(commit=False, request=request)
# Example (Might need some minor tinkering)
def myview(request):
if request.method == 'POST':
form = CompanyForm(request.POST)
if form.is_valid():
form.save(commit=True, request=request)
Normally I inject vars into the declaration + __init__, ex form = CompanyForm(request.POST, request=request, is_edit=True) instead of the save() but 1 look at contrib/admin/options.py + ModelAdmin.get_form() & no thanks!
You can use editable=False, eg,
modified_by = models.ForeignKey(User, related_name='company_modified_by', unique = False, on_delete=models.CASCADE, editable=False)
According to the docs, If False, the field will not be displayed in the admin or any other ModelForm. They are also skipped during model validation. Default is True.
That way, you can set it programmatically during creation (eg, via a request to a view) and not have to worry about it being edited.
def create_view(request):
if request.method == "POST":
company_form = CompanyForm(request.POST)
company_form.instance.created_by = request.user
company_form.save()
(Also - don't forget, use
modified_date = models.DateTimeField(auto_now=True)
for modified dates. auto_now_add is for one time creation updates.)
Related
I am doing an online classroom project in Django where I created a model named create_course which is accessible by teachers. Now I am trying to design this as the teacher who creates a class only he can see this after login another teacher shouldn't see his classes and how to add students into that particular class I created
the course model
class course(models.Model):
course_name = models.CharField(max_length=200)
course_id = models.CharField(max_length=10)
course_sec = models.IntegerField()
classroom_id = models.CharField(max_length=50,unique=True)
views.py
def teacher_view(request, *args, **kwargs):
form = add_course(request.POST or None)
context = {}
if form.is_valid():
form.save()
return HttpResponse("Class Created Sucessfully")
context['add_courses'] = form
return render(request, 'teacherview.html', context)
forms.py
from django import forms
from .models import course
class add_course(forms.ModelForm):
class Meta:
model = course
fields = ('course_name', 'course_id', 'course_sec', 'classroom_id')
Add one more field in course model that establish relationship with User model. Hence you can get the details about the teacher who has created course.
from django.contrib.auth.models import User
class course(models.Model):
course_name = models.CharField(max_length=200)
course_id = models.CharField(max_length=10)
course_sec = models.IntegerField()
classroom_id = models.CharField(max_length=50,unique=True)
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
In your view function, you can check whether logged in user is same as the created of the requested course.
def teacher_view(request, *args, **kwargs):
# since this is course specific view, you will be passing an identiier or pk of the course as an argument to this function.
course_obj = Course.objects.get(id="identifier")
if request.user == course_obj.created_by:
# logged in user is same as the creator of the course
else:
# redirect
I would prefer creating permissions and having those in specific models. You can give it a try with that too. Let me know, if it doesn't work.
I'm new to Django and I've correctly created my first web app where I can register and login as an user. I'm using the standard from django.contrib.auth.models import User and UserCreationFormto manage this thing.
Now, I would like to create a new table in the database to add new fields to the user. I'm already using the standard one such as email, first name, second name, email, username, etc but I would like to extend it by adding the possibility to store the latest time the email has been changed and other info. All those info are not added via the form but are computed by the backend (for instance, every time I want to edit my email on my profile, the relative field on my new table, linked to my profile, change value)
To do that I have added on my models.py file the current code
from django.db import models
from django.contrib.auth.models import User
class UserAddInformation(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
last_time_email_change = models.TimeField('Last email changed', auto_now_add=True, blank=True)
def __str__(self):
return self.user.username
And on my admin.py
from django.contrib import admin
from .models import UserAddInformation
admin.site.register(UserAddInformation)
The form to edit the email and the view can be found below
forms.py
class EditUserForm(UserChangeForm):
password = None
email = forms.EmailField(widget=forms.EmailInput(attrs={
'class': 'form-control'
}))
class Meta:
model = User
# select the fields that you want to display
fields = ('email',)
views.py
#authenticated_user
def account_user(request):
if request.method == "POST":
form = EditUserForm(request.POST, instance=request.user)
if form.is_valid():
form.save()
user_info_obj = UserAddInformation.objects.create(user=request.user,
last_time_email_change=datetime.now())
user_info_obj.save()
messages.success(request, "Edit Succesfully")
else:
pass
else:
form = EditUserForm()
return render(request, 'authenticate/account.html', {
'form_edit': form,
})
The issue is that, once I'm going to update the email via the form, I got an error UNIQUE constraint failed: members_useraddinformation.user_id
Using ForeignKey make it works but it create a new row in the table, with the same when I just want to update the first one
The edit process for the email works tho
What am I doing wrong?
It turned out that auto_now_add=True inherit editable=False generating the error. So changing my models.py with
class UserAddInformation(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
last_time_email_change = models.TimeField('Last email changed')
def save(self, *args, **kwargs):
self.last_time_email_change = timezone.now()
return super(UserAddInformation, self).save(*args, **kwargs)
def __str__(self):
return self.user.username
and checking with
user_info_obj = UserAddInformation.objects.get_or_create(user=request.user)
user_info_obj[0].save()
inside def account_user(request): worked
I'm not sure it's the best solution for my issue tho
The dropdown list appears correctly in the html, However I am unable to figure out why I run into the same error time after time when I try to submit / .
"Select a valid choice. That choice is not one of the available choices."
the problem context
I have two models defined in Django. One CourseModel database to hold all the offered courses and one registration database to link a course to a user.
models.py
from django.db import models
# Create your models here.
class CourseModel(models.Model):
course = models.CharField(max_length=100)
date = models.DateField(max_length=100)
time = models.TimeField()
location = models.CharField(max_length=100)
datetime = models.DateTimeField()
class RegistrationModel(models.Model):
name = models.CharField(max_length=100)
adress = models.CharField(max_length=100)
city = models.CharField(max_length=100)
email = models.EmailField(max_length=100)
course = models.ForeignKey('self', on_delete=models.CASCADE)
def __str__(self):
return self.name
I use modelForm to create a registration form, where the user can subscribe for a course from a dropdown list.
forms.py
from django.forms import ModelForm, RegexField
from home.models import RegistrationModel, CourseModel
from django import forms
import datetime
class RegistrationForm(ModelForm):
def __init__(self, *args, **kwargs):
super(RegistrationForm, self).__init__(*args, **kwargs)
self.fields['course'].queryset = CourseModel.objects.exclude(date__lt=datetime.datetime.today()).values_list('datetime', flat=True)
self.fields['course'].empty_label = None
class Meta:
model = RegistrationModel
fields = '__all__'
views.py
from django.shortcuts import render, redirect
from home.forms import RegistrationForm
from .models import CourseModel
import datetime
def home(request):
return render(request, 'home/home.html')
def registration(request):
if request.method == 'POST':
form = RegistrationForm(request.POST)
crs = request.POST.get('course')
print(crs)
if form.is_valid():
cleanform = form.save(commit=False)
cleanform.course = crs
cleanform.save()
return redirect('home')
else:
form = RegistrationForm()
return render(request, 'home/registration.html', {'form': form})
In the RegistrationForm's __init__() method, your self.fields['course'].queryset = ...values_list('datetime', flat=True) returns datetime instances. See values_list() docs.
I believe this may cause the issue. I guess the queryset should return CourseModel instances, based on the Django docs:
ForeignKey is represented by django.forms.ModelChoiceField, which is a ChoiceField whose choices are a model QuerySet.
Also, your RegistrationModel.course field has a foreign key to 'self' instead of the CourseModel. Not sure if that is what you want.
Other examples of setting the field queryset can be found here.
I have these models. Each reply can have none, one or more post. Post is user specific. How to make delete view so that user can only delete his post and not of posts by other on a reply.
I tried this many time but my view is deleting post of some other user as well. Means any user can delete post of any other user.
I want to make a button next to each post to delete, but button should be seen only to those who have written the post.
class Reply(models.Model):
User = models.ForeignKey(settings.AUTH_USER_MODEL)
Question = models.ForeignKey(Doubt, on_delete=models.CASCADE)
reply = models.TextField(max_length=40000)
last_updated = models.DateTimeField(auto_now_add=True)
image = models.ImageField(upload_to = upload_image_path, null = True, blank = True)
created_at = models.DateTimeField(auto_now_add=True)
def Post(self):
return reverse("community:post", kwargs={"pk": self.pk})
class Post(models.Model):
post = models.TextField(max_length=4000)
reply = models.ForeignKey(Reply, on_delete = models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
time = models.DateTimeField(null=True)
User = models.ForeignKey(settings.AUTH_USER_MODEL)
If you have the AuthenticationMiddleware enabled in settings.py, the request object in a view function will include a user model. Your view will then look something like this:
from django import http
def get_post_from_request(request):
... something to pull up the post object from the request ...
return the_post
def delete_post(request):
the_post = get_post_from_request(request)
if request.user == the_post.User:
the_post.delete()
return http.HttpResponseRedirect("/your/success/url/")
else:
return http.HttpResponseForbidden("Cannot delete other's posts")
If you're using generic class based views, your view might look more like this:
from django.views.generic import DeleteView
from django import http
class PostView(DeleteView):
model = Post
success_url = '/your/success/url/'
# override the delete function to check for a user match
def delete(self, request, *args, **kwargs):
# the Post object
self.object = self.get_object()
if self.object.User == request.user:
success_url = self.get_success_url()
self.object.delete()
return http.HttpResponseRedirect(success_url)
else:
return http.HttpResponseForbidden("Cannot delete other's posts")
If you'd like help navigating class based views (they have a dense inheritance hierarchy) I can recommend http://ccbv.co.uk - their breakdown on the Delete view is here
I have a database of articles with a
submitter = models.ForeignKey(User, editable=False)
Where User is imported as follows:
from django.contrib.auth.models import User.
I would like to auto insert the current active user to the submitter field when a particular user submits the article.
Anyone have any suggestions?
Just in case anyone is looking for an answer, here is the solution i've found here:
http://demongin.org/blog/806/
To summarize:
He had an Essay table as follows:
from django.contrib.auth.models import User
class Essay(models.Model):
title = models.CharField(max_length=666)
body = models.TextField()
author = models.ForeignKey(User, null=True, blank=True)
where multiuser can create essays, so he created a admin.ModelAdmin class as follows:
from myapplication.essay.models import Essay
from django.contrib import admin
class EssayAdmin(admin.ModelAdmin):
list_display = ('title', 'author')
fieldsets = [
(None, { 'fields': [('title','body')] } ),
]
def save_model(self, request, obj, form, change):
if getattr(obj, 'author', None) is None:
obj.author = request.user
obj.save()
Let's say that user B saves a record created by user A. By using this approach above the record will be saved with user B. In some scenarios this might not be the best choice, because each user who saves that record will be "stealing" it. There's a workaround to this, that will save the user only once (the one who creates it):
models.py
from django.contrib.auth.models import User
class Car(models.Model):
created_by = models.ForeignKey(User,editable=False,null=True,blank=True)
car_name = models.CharField(max_length=40)
admin.py
from . models import *
class CarAdmin(admin.ModelAdmin):
list_display = ('car_name','created_by')
actions = None
def save_model(self, request, obj, form, change):
if not obj.created_by:
obj.created_by = request.user
obj.save()
If you don't want to keep foreignkey in you model to user, then in your admin.py override save method
obj.author = request.user.username
obj.save()
This will store the username who is logged in your db.
It's time for a better solution override the get_form method
let's say we have this model
models.py
from django.db import models
from django.contrib.auth.models import User
class Post(models.Model):
title = models.CharField(max_length=256)
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return self.title
admin.py
class PostAdmin(admin.ModelAdmin):
# you should prevent author field to be manipulated
readonly_fields = ['author']
def get_form(self, request, obj=None, **kwargs):
# here insert/fill the current user name or id from request
Post.author = request.user
return super().get_form(request, obj, **kwargs)
def save_model(self, request, obj, form, change):
obj.author = request.user
obj.last_modified_by = request.user
obj.save()
admin.site.register(Post, PostAdmin)
As per http://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.prepopulated_fields you can't use ForeignKey with the prepopulated_field admin directive, alas
But this thread might help you. In my answer I also link to a Google-scanned version of Pro Django, which has a great solution for this kind of thing. Ideally, am sure it's better if you can buy the book, but Google seems to have most of the relevant chapter anyway.
You can't do it directly. However you can achieve this creating middleware and using current user as global variable. But there is a package already doing it : django-currentuser
First install it then
setting.py
MIDDLEWARE = (
...,
'django_currentuser.middleware.ThreadLocalUserMiddleware',
)
and import it in the model file
from django_currentuser.middleware import ( get_current_user, get_current_authenticated_user)
And use;
class Foo(models.Model):
created_by = CurrentUserField()
updated_by = CurrentUserField(on_update=True)