Django localization use language from user model - django

I'm trying to take user language from UserProfile model. User choosing language in form, and after that website have to be translated on that language. I created switcher which can change language but it's not what i need because it doesn't communicate with my model. I know that i have to rewrite LocaleMiddleware, but i don't know how to do it in right way. I've tried some solution from stackoverflow, but it doesn't work for me.
So the question is: what i'm doing wrong? or maybe there is no problem with middleware but problem somewhere else?
My middleware:
from django.utils import translation
class LocaleMiddleware(object):
"""
This is a very simple middleware that parses a request
and decides what translation object to install in the current
thread context. This allows pages to be dynamically
translated to the language the user desires (if the language
is available, of course).
"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
language_code = request.LANGUAGE_CODE
translation.activate(language_code)
response = self.get_response(request)
translation.deactivate()
return response
def process_request(self, request):
language = translation.get_language_from_request(request)
translation.activate(language)
request.LANGUAGE_CODE = translation.get_language()
def process_response(self, request, response):
translation.deactivate()
return response
My model:
from core.settings.base import LANGUAGES, LANGUAGE_CODE
class UserProfile(models.Model):
user = models.OneToOneField(
User, on_delete=models.CASCADE, related_name="user_profile"
)
phone_number = models.CharField(
max_length=16,
blank=True,
null=True,
validators=[
RegexValidator(
regex=r"^\+?1?\d{9,15}$",
message="Phone number must be entered in the format '+123456789'. Up to 15 digits allowed.",
),
],
)
language = models.CharField(
choices=LANGUAGES, default=LANGUAGE_CODE, max_length=10
)
objects = models.Manager()

Finally i found an answer.
from django.utils import translation
from django.utils.deprecation import MiddlewareMixin
class CustomLocaleMiddleware(MiddlewareMixin):
def process_request(self, request):
try:
if request.user.is_authenticated:
translation.activate(request.user.user_profile.language)
request.LANGUAGE_CODE = request.user.user_profile.language
except:
request.LANGUAGE_CODE = "en"

Related

Cannot change superusers own django admin inline

I have recently updated from django 2.0.4 to 3.0.5.
I have a UserAdmin with the following inline:
class PreferencesInline(admin.StackedInline):
model = Preferences
can_delete = False
classes = ['collapse']
When I login as a superuser, I can change the preferences of other users through the inline, but not my own. Why is that? On our server with django 2.0.4 I can both change other users preferences but also my own preferences through the inline. I could not find any explanation for this strange behaviour...
EDIT The same behaviour applies when I try to change my Preferences directly in the admin, so this is not specifically an issue with the inline.
EDIT2 The problem is the following middleware. Any ideas how it could be modified to solve the problem?
class UserLanguageMiddleware:
""" Middleware to set user language """
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
if request.user.is_authenticated:
user_language = request.user.profile.language
activate(user_language)
response = self.get_response(request)
return response
By following the official Django advice to explicity setting the active language, the problem was solved:
settings.py:
LANGUAGE_COOKIE_NAME = 'language'
middleware.py:
class UserLanguageMiddleware:
""" Middleware to set user language cookie """
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
if request.user.is_authenticated and not request.COOKIES.get(settings.LANGUAGE_COOKIE_NAME):
# Set cookie
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, request.user.profile.language)
elif not request.user.is_authenticated and request.COOKIES.get(settings.LANGUAGE_COOKIE_NAME):
# Delete cookie
response.delete_cookie(settings.LANGUAGE_COOKIE_NAME)
return response
models.py
from django.dispatch import receiver
from django.contrib.auth.signals import user_logged_in
# Update user language on login
#receiver(user_logged_in)
def post_login(sender, user, request, **kwargs):
user_language = user.profile.language
translation.activate(user_language)

Django Model Terms of Service With Timestamp, Username, and Prompt User To Agree To Updated Terms of Service

I would like to create a model that contains a timestamp and the allauth currently logged in user who agreed to the Terms of Service. Then, on every page (if the user is logged in), annotate if the user has agreed to the latest Terms of Service (by comparing the timestamp of their last agreement to the timestamp of the latest updated Terms of Service), and if the user has not agreed to the most recent Terms of Service version they are redirected to a page that requires them to agree to the updated version. Then it redirects the user back to whence they came after they agree.
How does one go about creating something like this?
What I have so far is below.
Models.py:
from django.contrib.auth.models import User
class TermsOfService(models.Model):
agreement = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True,blank=True, null=True)
user = models.ForeignKey(User, blank=True, null=True, on_delete=models.CASCADE)
def __str__(self):
return self.agreement
class UserMembership(models.Model):
user = models.OneToOneField(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
stripe_customer_id = models.CharField(max_length=40, unique=True)
membership = models.ForeignKey(
Membership, on_delete=models.SET_NULL, null=True)
def __str__(self):
return self.user.username
Forms.py:
from .models import TermsOfService
class TermsOfServiceForm(forms.ModelForm):
class Meta:
model = TermsOfService
fields = ('agreement',)
def __init__(self, *args, **kwargs):
super(TermsOfServiceForm, self).__init__(*args, **kwargs)
self.fields['agreement'].widget.attrs={ 'id': 'agreement_field', 'class': 'form-control', 'required': 'true', 'autocomplete':'off'}
App Urls.py:
from django.urls import path
from .views import ( terms_of_service_view )
app_name = 'app'
urlpatterns = [ path('terms_of_service_view/', terms_of_service_view, name='terms_of_service_view'), ]
Views.py:
def get_user_membership(request):
user_membership_qs = UserMembership.objects.filter(user=request.user)
if user_membership_qs.exists():
return user_membership_qs.first()
return None
def terms_of_service_view(request):
if request.method == 'POST':
form = TermsOfServiceForm(request.POST)
if form.is_valid():
user_membership = get_user_membership(request)
instance = form.save(commit=False)
instance.user = request.user
instance.save()
context = {
'user_membership': user_membership,
'form':form
}
return render(request, "index.html", context)
else:
form = TermsOfServiceForm()
context = {
'user_membership': user_membership,
'form': form,
}
return render(request, "index.html", context)
A question arises from your code, like how are you going to determine when user needs to agree to agreement, do you create a bunch of new entry in TermsOfService. Rather than that, why not create a new model named Terms and add it as ForeignKey.
class Term(models.Model):
text = models.TextField()
created_at = models.DateTimeField(auto_now_add=True,blank=True, null=True)
# blah blah
class TermsOfService(models.Model):
term = models.ForeignKey(Term, on_delete=models.DO_NOTHING)
agreement = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True,blank=True, null=True)
user = models.ForeignKey(User, blank=True, null=True, on_delete=models.CASCADE)
There is an advantage of taking this approach, that is all you need to do is create a new Term object, and rest can be taken care of by middleware. For example:
from django.urls import reverse
from django.shortcuts import redirect
class TermAgreeMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
if not request.user.is_authenticated:
return response
term_date = Term.objects.last().created_at
user_term_date = request.user.termofservice_set.filter(created_at__gte=term_date).exists()
if not user_term_date:
return redirect(reverse('app:terms_of_service_view')+'?next='+request.path)
return response
And update the view:
def terms_of_service_view(request):
if request.method == 'POST':
form = TermsOfServiceForm(request.POST)
if form.is_valid():
user_membership = request.user.usermembership # you don't need another view as User has OneToOne relation with UserMembership
instance = form.save(commit=False)
instance.user = request.user
instance.term = Term.objects.last()
instance.save()
go_next = request.GET.get('next', None) # handle redirection
if go_next:
return redirect(go_next)
context = {
'user_membership': user_membership,
'form':form
}
return render(request, "index.html", context)
else:
form = TermsOfServiceForm()
context = {
'user_membership': user_membership,
'form': form,
}
return render(request, "index.html", context)
Finally add that TermAgreeMiddleware in MIDDLEWARE settings. So everytime you want users to agree a new term, just create a new Term instance(from admin site or shell).

How to increase the visits only the first time the user accesses the page?

I want to do the following: every time a user enters an article, it increments the visit by +1, but only the first time the user logs in, so I have to save the fact that the user has already entered the page in some place. But I'm not using authentication or anything like that.
I know I can use javascript to store in LocalStorage, but I still do not know how to work with APIS in the back end.
What's the easiest way to do this on the backend?
Currently the function that increments is as below. NOTE: I create a new object instead of using something like "instance.visits + = 1" because I need to save the date of each visit to filter the posts with more visits in a certain period of time, and that was the only way I got it.
class ArticlePage(Page):
# ....
def serve(self, request, *args, **kwargs):
request.is_preview = getattr(request, 'is_preview', False)
self.views.create(date=datetime.datetime.now())
self.save()
print(self.views.all().count())
return TemplateResponse(
request,
self.get_template(request, *args, **kwargs),
self.get_context(request, *args, **kwargs)
)
class ArticlePageViews(models.Model):
article = models.ForeignKey(
ArticlePage,
on_delete=models.CASCADE,
related_name='views'
)
date = models.DateTimeField()
def __str__(self):
return f'{self.date}'
Here is the possibility using coockies
class ArticlePage(Page):
# ....
def serve(self, request, *args, **kwargs):
request.is_preview = getattr(request, 'is_preview', False)
if not 'cookie_name' in request.COOKIES:
self.views.create(date=datetime.datetime.now())
self.save()
print(self.views.all().count())
response=TemplateResponse(
request,
self.get_template(request, *args, **kwargs),
self.get_context(request, *args, **kwargs)
)
response.set_cookie('cookie_name', 'cookie_value')
return response
refer this to set expire time for coockie
Since you only want one view per user, you can't store the views in the user session, because the session changes when a user logs in. I would suggest you set a UUID cookie the first time a user visits your website and set a long expiration date, like 10 years. This can be done with a middleware:
# middleware.py
import uuid
class UUIDMiddleware:
"""
Middleware to set a UUID cookie when a user visits
for the first time.
"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
if not request.COOKIES.get('user_uuid'):
user_uuid = str(uuid.uuid4())
# set cookie in request so it can be used on the first visit
request.COOKIES['user_uuid'] = user_uuid
response = self.get_response(request)
max_age = 60 * 60 * 24 * 365 * 10 # 10 years
response.set_cookie('user_uuid', user_uuid, max_age=max_age)
return response
return self.get_response(request)
Remember to add your middleware to the settings. Now in your ArticlePageViews model you can set a unique_together constraint:
class ArticlePageViews(models.Model):
article = models.ForeignKey(
ArticlePage,
on_delete=models.CASCADE,
related_name='views'
)
user_uuid = models.UUIDField()
date = models.DateTimeField(auto_now_add=True) # set datetime automatically
class Meta:
unique_together = ('article', 'user_uuid')
def __str__(self):
return f'{self.date}'
and in your view (just an example):
def article_view(request, article_id):
article = get_object_or_404(ArticlePage, pk=article_id)
user_uuid = request.COOKIES.get('uuid_cookie')
if user_uuid is not None: # user_uuid may not be set yet
try:
ArticlePageViews.objects.create(article=artricle, user_uuid=user_uuid)
except IntegrityError:
# unique_together validation failed, so the user already viewed this article
pass # don't do anything
...

Django inline formset, once all inlines full, the form creates an extra entry and saves it to the db

Here is my code,
views.py:
from django.views.generic import TemplateView, View, UpdateView
from django.contrib.auth.decorators import login_required
from django.shortcuts import redirect
from django.core.urlresolvers import reverse
PurchaserChoiceFormset = inlineformset_factory(Worksheet, PurchaserChoice, form=PurchaserChoiceForm, can_delete=False,extra=5, max_num=5)
from models import Worksheet
from forms import PurchaserChoiceFormset
class WorksheetStep1View(TemplateView):
template_name = 'worksheets/step1.html'
def get_context_data(self, **kwargs): # Exec 1st
context = super(WorksheetStep1View, self).get_context_data(**kwargs)
context['worksheet'] = Worksheet.objects.get(pk=self.kwargs.get('worksheet_id'))
context['choice_formset'] = PurchaserChoiceFormset(self.request.POST or None, instance=context['worksheet'])
return context
def post(self, request, *args, **kwargs):
context = self.get_context_data()
if context['choice_formset'].is_valid():
context['choice_formset'].instance = context['worksheet']
context['choice_formset'].save()
return super(WorksheetStep1View, self).render_to_response(context)
models.py:
from django.db import models
class Worksheet(models.Model):
completed_date = models.DateTimeField(null=True, blank=True)
class PurchaserChoice(models.Model): # Choice made by the purchaser for model/floor
model = models.CharField(blank=True, null=True, max_length=255)
floor = models.CharField(blank=True, null=True, max_length=255) # coming from API
worksheet = models.ForeignKey('worksheets.Worksheet')
def __unicode__(self):
return "PurchaseChoiceID {0}, WorksheetID {1} - Model: {2} - Floor: {3}".format(self.id, self.worksheet.id,
self.model, self.floor, )
class Meta:
#ordering = ('-model', 'floor')
ordering = ('-id', )
I want only up to 5 instances of the purchase choice to exist, but after the form is full and saved again, a new entry is created. If i get rid of context['choice_formset'].instance = context['worksheet'] , it is even worse, it creates a new entry for every instance in the formset.
Can anyone help me figure out why this is happening? I also looked at UpdateView but seems like its got more things to it than I need and I don't think its the cause to my problem. I am not sure why its creating new entries instead of updating once the form has 5 entries in it and saved again.
One way I avoid this problem is just to delete all the choices on save each time, like so:
def post(self, request, *args, **kwargs):
context = self.get_context_data()
if context['choice_formset'].is_valid():
PurchaserChoice.objects.filter(worksheet=context['worksheet']).delete()
#context['choice_formset'].instance = context['worksheet']
context['choice_formset'].save()
return super(WorksheetStep1View, self).render_to_response(context)
So what I found wrong was that I was returning the wrong render_to_response (of the parent class with the context) which caused me to create new objects every time because the instance was lost. This is what fixed it:
return self.render_to_response(context)
instead of:
return super(WorksheetStep1View, self).render_to_response(context)
And this also did not work:
return TemplateResponse(request, self.template_name, context)
I'll see if I can get a better idea of why TemplateResponse did not work.

Django: Populate user ID when saving a model

I have a model with a created_by field that is linked to the standard Django User model. I need to automatically populate this with the ID of the current User when the model is saved. I can't do this at the Admin layer, as most parts of the site will not use the built-in Admin. Can anyone advise on how I should go about this?
UPDATE 2020-01-02
⚠ The following answer was never updated to the latest Python and Django versions. Since writing this a few years ago packages have been released to solve this problem. Nowadays I highly recommend using django-crum which implements the same technique but has tests and is updated regularly: https://pypi.org/project/django-crum/
The least obstrusive way is to use a CurrentUserMiddleware to store the current user in a thread local object:
current_user.py
from threading import local
_user = local()
class CurrentUserMiddleware(object):
def process_request(self, request):
_user.value = request.user
def get_current_user():
return _user.value
Now you only need to add this middleware to your MIDDLEWARE_CLASSES after the authentication middleware.
settings.py
MIDDLEWARE_CLASSES = (
...
'django.contrib.auth.middleware.AuthenticationMiddleware',
...
'current_user.CurrentUserMiddleware',
...
)
Your model can now use the get_current_user function to access the user without having to pass the request object around.
models.py
from django.db import models
from current_user import get_current_user
class MyModel(models.Model):
created_by = models.ForeignKey('auth.User', default=get_current_user)
Hint:
If you are using Django CMS you do not even need to define your own CurrentUserMiddleware but can use cms.middleware.user.CurrentUserMiddleware and the cms.utils.permissions.get_current_user function to retrieve the current user.
If you want something that will work both in the admin and elsewhere, you should use a custom modelform. The basic idea is to override the __init__ method to take an extra parameter - request - and store it as an attribute of the form, then also override the save method to set the user id before saving to the database.
class MyModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
return super(MyModelForm, self).__init__(*args, **kwargs)
def save(self, *args, **kwargs):
kwargs['commit']=False
obj = super(MyModelForm, self).save(*args, **kwargs)
if self.request:
obj.user = self.request.user
obj.save()
return obj
Daniel's answer won't work directly for the admin because you need to pass in the request object. You might be able to do this by overriding the get_form method in your ModelAdmin class but it's probably easier to stay away from the form customisation and just override save_model in your ModelAdmin.
def save_model(self, request, obj, form, change):
"""When creating a new object, set the creator field.
"""
if not change:
obj.creator = request.user
obj.save()
This whole approach bugged the heck out of me. I wanted to say it exactly once, so I implemented it in middleware. Just add WhodidMiddleware after your authentication middleware.
If your created_by & modified_by fields are set to editable = False then you will not have to change any of your forms at all.
"""Add user created_by and modified_by foreign key refs to any model automatically.
Almost entirely taken from https://github.com/Atomidata/django-audit-log/blob/master/audit_log/middleware.py"""
from django.db.models import signals
from django.utils.functional import curry
class WhodidMiddleware(object):
def process_request(self, request):
if not request.method in ('GET', 'HEAD', 'OPTIONS', 'TRACE'):
if hasattr(request, 'user') and request.user.is_authenticated():
user = request.user
else:
user = None
mark_whodid = curry(self.mark_whodid, user)
signals.pre_save.connect(mark_whodid, dispatch_uid = (self.__class__, request,), weak = False)
def process_response(self, request, response):
signals.pre_save.disconnect(dispatch_uid = (self.__class__, request,))
return response
def mark_whodid(self, user, sender, instance, **kwargs):
if 'created_by' in instance._meta.fields and not instance.created_by:
instance.created_by = user
if 'modified_by' in instance._meta.fields:
instance.modified_by = user
here's how I do it with generic views:
class MyView(CreateView):
model = MyModel
def form_valid(self, form):
object = form.save(commit=False)
object.owner = self.request.user
object.save()
return super(MyView, self).form_valid(form)
If you are using class based views Daniel's answer needs more. Add the following to ensure that the request object is available for us in your ModelForm object
class BaseCreateView(CreateView):
def get_form_kwargs(self):
"""
Returns the keyword arguments for instanciating the form.
"""
kwargs = {'initial': self.get_initial()}
if self.request.method in ('POST', 'PUT'):
kwargs.update({
'data': self.request.POST,
'files': self.request.FILES,
'request': self.request})
return kwargs
Also, as already mentioned, you need to return the obj at the end of ModelForm.save()
what is the problem with using something like:
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
exclude = ['created_by']
def save(self, user):
obj = super().save(commit = False)
obj.created_by = user
obj.save()
return obj
Now call it like myform.save(request.user) in the views.
here is ModelForm's save function, which has only a commit parameter.
For future references, best solution I found about this subject:
https://pypi.python.org/pypi/django-crum/0.6.1
This library consist of some middleware.
After setting up this libary, simply override the save method of model and do the following,
from crum import get_current_user
def save(self, *args, **kwargs):
user = get_current_user()
if not self.pk:
self.created_by = user
else:
self.changed_by = user
super(Foomodel, self).save(*args, **kwargs)
if you create and abstract model and inherit from it for all your model, you get your auto populated created_by and changed_by fields.
Based on bikeshedder's answer, I found a solution since his did not actually work for me.
app/middleware/current_user.py
from threading import local
_user = local()
class CurrentUserMiddleware(object):
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
_user.value = request.user
return self.get_response(request)
def get_current_user():
return _user.value
settings.py
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'common.middleware.current_user.CurrentUserMiddleware',
]
model.py
from common.middleware import current_user
created_by = models.ForeignKey(User, blank=False, related_name='created_by', editable=False, default=current_user.get_current_user)
I'm using python 3.5 and django 1.11.3
From the Django documentation Models and request.user:
" To track the user that created an object using a CreateView, you can
use a custom ModelForm. In the view, ensure that you
don’t include [the user field] in the list of fields to edit, and override
form_valid() to add the user:
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)
The 'save' method from forms.ModelForm returns the saved instanced.
You should add one last line to MyModelForm:
...
return obj
This change is necessary if you are using create_object or update_object generic views.
They use the saved object to do the redirect.
I don't believe Daniel's answer is the best there is since it changes the default behaviour of a model form by always saving the object.
The code I would use:
forms.py
from django import forms
class MyModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user', None)
super(MyModelForm, self).__init__(*args, **kwargs)
def save(self, commit=True):
obj = super(MyModelForm, self).save(commit=False)
if obj.created_by_id is None:
obj.created_by = self.user
if commit:
obj.save()
return obj
Note sure if you were looking for this, but adding the following
user = models.ForeignKey('auth.User')
to a model will work to add the user id to the model.
In the following, each hierarchy belongs to a user.
class Hierarchy(models.Model):
user = models.ForeignKey('auth.User')
name = models.CharField(max_length=200)
desc = models.CharField(max_length=1500)