Adding custom functionality to django router methods - django

I'm trying to add custom functionality to django router methods.
This is my router that exposes the standard methods on an user.
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = [BasePermission]
I'm validating the user using serializer validation methods.
class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
MOBILE_ERROR = 'Mobile number should be 10 digits long and only contain numbers.'
EMAIL_ERROR = 'Incorrect email format'
USERNAME_ERROR = 'Username must be at least 6 characters long and contain only letters and numbers.'
class Meta:
model = User
fields = '__all__'
def validate_mobile(self, value):
regexp = re.compile(r'^[0-9]{10}$')
if regexp.search(value):
return value
raise serializers.ValidationError(self.MOBILE_ERROR)
def validate_email(self, value):
if validate_email(value):
return value
raise serializers.ValidationError(self.EMAIL_ERROR)
def validate_username(self, value):
regexp = re.compile(r'^[a-zA-Z0-9]{6,}$')
if regexp.search(value):
return value
raise serializers.ValidationError(self.USERNAME_ERROR)
And this is my route.
router = DefaultRouter(trailing_slash=False)
router.register(r'user', UserViewSet),
urlpatterns = router.urls
I want to add a method send_activation_code if the user is created successfully. How do I do this?

For such purpose you can use signals. Every time when your app creates new User instance - some action should be performed. In your case you should connect build-in signal post_save and your existed send_activation_code function
Example for your case:
yourapp/signals.py:
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
#receiver(post_save, sender=User)
def send_activation_code_signal(sender, instance, created, **kwargs):
if created:
send_activation_code(instance.phone_number)
Also, you need to import signals in your app config file
yourapp/app.py:
from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _
class YourAppConfig(AppConfig):
name = 'yourproject.yourapp'
verbose_name = _('yourapp')
def ready(self):
import yourproject.yourapp.signals
yourapp/__init__.py:
default_app_config = 'yourproject.yourapp.apps.YourAppConfig'
If you dont need to send code every time User instance created - you can specify more statements, for example:
if created and instance.validated:
send_activation_code(instance.phone_number)
There are some more useful built-in signals in Django, check docs
Django signals docs: https://docs.djangoproject.com/en/3.0/ref/signals/

Related

Django: extended user with proxy model to add extra method, how to use the method in template?

I extend the default user model with a proxy model to add an extra method.
from django.contrib.auth.models import User
class Person(User):
class Meta:
proxy = True
def custom_method(self):
pass
The main purpose is to use the method in templates.
<div>{{ user.custom_method }}</div>
But since the user is pointing to the default user model, it has no access to the custom_method.
Is there any way to achieve this other than create a subclass of the User model?
=== UPDATE ==============
I ended up with custom backends:
(not sure if this solution has any drawbacks)
# user proxy
class ExtendedUser(get_user_model()):
class Meta:
proxy = True
def custom_method(self):
...
# backends
from django.contrib.auth.backends import ModelBackend
# since I'm using allauth
from allauth.account.auth_backends import AuthenticationBackend
from .models import ExtendedUser
class DjangoModelBackend(ModelBackend):
def get_user(self, user_id):
print("\n\n\ncustom user!!!")
try:
user = ExtendedUser.objects.get(pk=user_id)
except ExtendedUser.DoesNotExist:
return None
return user if self.user_can_authenticate(user) else None
class AuthModelBackend(DjangoModelBackend, AuthenticationBackend):
pass
And in settings.py, add these to AUTHENTICATION_BACKENDS.
It might be better to monkey patch the user model. Indeed, in one of the AppConfigs [Django-doc] we can implement this with:
# app_name/apps.py
from django.apps import AppConfig
from django.contrib.auth import get_user_model
def custom_method(self):
return 'Some value'
class MyAppConfig(AppConfig):
name = 'app_name'
def ready(self):
User = get_user_model()
User.custom_method = custom_method
Here we thus add a method to the user model that can then be called in views, templates, etc.

How to get user in Django signals [duplicate]

I have done the below post_save signal in my project.
from django.db.models.signals import post_save
from django.contrib.auth.models import User
# CORE - SIGNALS
# Core Signals will operate based on post
def after_save_handler_attr_audit_obj(sender, **kwargs):
print User.get_profile()
if hasattr(kwargs['instance'], 'audit_obj'):
if kwargs['created']:
kwargs['instance'].audit_obj.create(operation="INSERT", operation_by=**USER.ID**).save()
else:
kwargs['instance'].audit_obj.create(operation="UPDATE").save()
# Connect the handler with the post save signal - Django 1.2
post_save.connect(after_save_handler_attr_audit_obj, dispatch_uid="core.models.audit.new")
The operation_by column, I want to get the user_id and store it. Any idea how can do that?
Can't be done. The current user is only available via the request, which is not available when using purely model functionality. Access the user in the view somehow.
I was able to do it by inspecting the stack and looking for the view then looking at the local variables for the view to get the request. It feels like a bit of a hack, but it worked.
import inspect, os
#receiver(post_save, sender=MyModel)
def get_user_in_signal(sender, **kwargs):
for entry in reversed(inspect.stack()):
if os.path.dirname(__file__) + '/views.py' == entry[1]:
try:
user = entry[0].f_locals['request'].user
except:
user = None
break
if user:
# do stuff with the user variable
Ignacio is right. Django's model signals are intended to notify other system components about events associated with instances and their respected data, so I guess it's valid that you cannot, say, access request data from a model post_save signal, unless that request data was stored on or associated with the instance.
I guess there are lots of ways to handle it, ranging from worse to better, but I'd say this is a prime example for creating class-based/function-based generic views that will automatically handle this for you.
Have your views that inherit from CreateView, UpdateView or DeleteView additionally inherit from your AuditMixin class if they handle verbs that operate on models that need to be audited. The AuditMixin can then hook into the views that successfully create\update\delete objects and create an entry in the database.
Makes perfect sense, very clean, easily pluggable and gives birth to happy ponies. Flipside? You'll either have to be on the soon-to-be-released Django 1.3 release or you'll have to spend some time fiddlebending the function-based generic views and providing new ones for each auditing operation.
You can do that with the help of middleware. Create get_request.py in your app. Then
from threading import current_thread
from django.utils.deprecation import MiddlewareMixin
_requests = {}
def current_request():
return _requests.get(current_thread().ident, None)
class RequestMiddleware(MiddlewareMixin):
def process_request(self, request):
_requests[current_thread().ident] = request
def process_response(self, request, response):
# when response is ready, request should be flushed
_requests.pop(current_thread().ident, None)
return response
def process_exception(self, request, exception):
# if an exception has happened, request should be flushed too
_requests.pop(current_thread().ident, None)
Then add this middleware to your settings:
MIDDLEWARE = [
....
'<your_app>.get_request.RequestMiddleware',
]
Then add import to your signals:
from django.db.models.signals import post_save
from django.contrib.auth.models import User
from <your_app>.get_request import current_request
# CORE - SIGNALS
# Core Signals will operate based on post
def after_save_handler_attr_audit_obj(sender, **kwargs):
print(Current User, current_request().user)
print User.get_profile()
if hasattr(kwargs['instance'], 'audit_obj'):
if kwargs['created']:
kwargs['instance'].audit_obj.create(operation="INSERT", operation_by=**USER.ID**).save()
else:
kwargs['instance'].audit_obj.create(operation="UPDATE").save()
# Connect the handler with the post save signal - Django 1.2
post_save.connect(after_save_handler_attr_audit_obj, dispatch_uid="core.models.audit.new")
Why not adding a middleware with something like this :
class RequestMiddleware(object):
thread_local = threading.local()
def process_request(self, request):
RequestMiddleware.thread_local.current_user = request.user
and later in your code (specially in a signal in that topic) :
thread_local = RequestMiddleware.thread_local
if hasattr(thread_local, 'current_user'):
user = thread_local.current_user
else:
user = None
For traceability add two attributes to your Model(created_by and updated_by), in "updated_by" save the last user who modified the record. Then in your signal you have the user:
models.py:
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
created_by = models. (max_length=100)
updated_by = models. (max_length=100)
views.py
p = Question.objects.get(pk=1)
p.question_text = 'some new text'
p.updated_by = request.user
p.save()
signals.py
#receiver(pre_save, sender=Question)
def do_something(sender, instance, **kwargs):
try:
obj = Question.objects.get(pk=instance.pk)
except sender.DoesNotExist:
pass
else:
if not obj.user == instance.user: # Field has changed
# do something
print('change: user, old=%s new=%s' % (obj.user, instance.user))
You could also use django-reversion for this purpose, e.g.
from reversion.signals import post_revision_commit
import reversion
#receiver(post_save)
def post_revision_commit(sender, **kwargs):
if reversion.is_active():
print(reversion.get_user())
Read more on their API https://django-reversion.readthedocs.io/en/stable/api.html#revision-api
You can do a small hack by overriding you model save() method and setting the user on the saved instance as additional parameter. To get the user I used get_current_authenticated_user() from django_currentuser.middleware.ThreadLocalUserMiddleware (see https://pypi.org/project/django-currentuser/).
In your models.py:
from django_currentuser.middleware import get_current_authenticated_user
class YourModel(models.Model):
...
...
def save(self, *args, **kwargs):
# Hack to pass the user to post save signal.
self.current_authenticated_user = get_current_authenticated_user()
super(YourModel, self).save(*args, **kwargs)
In your signals.py:
#receiver(post_save, sender=YourModel)
def your_model_saved(sender, instance, **kwargs):
user = getattr(instance, 'current_authenticated_user', None)
PS: Don't forget to add 'django_currentuser.middleware.ThreadLocalUserMiddleware' to your MIDDLEWARE_CLASSES.
I imagine you would have figured this out, but I had the same problem and I realised that all the instances I create had a reference to the user that creates them (which is what you are looking for)
it's possible i guess.
in models.py
class _M(models.Model):
user = models.ForeignKey(...)
in views.py
def _f(request):
_M.objects.create(user=request.user)
in signals.py
#receiver(post_save, sender=_M)
def _p(sender, instance, created, **kwargs):
user = instance.user
No ?
Request object can be obtained from frame record by inspecting.
import inspect
request = [
frame_record[0].f_locals["request"]
for frame_record in inspect.stack()
if frame_record[3] == "get_response"
][0]
def get_requested_user():
import inspect
for frame_record in inspect.stack():
if frame_record[3] == 'get_response':
request = frame_record[0].f_locals['request']
return request.user
else:
return None
context_processors.py
from django.core.cache import cache
def global_variables(request):
cache.set('user', request.user)
----------------------------------
in you model
from django.db.models.signals import pre_delete
from django.dispatch import receiver
from django.core.cache import cache
from news.models import News
#receiver(pre_delete, sender=News)
def news_delete(sender, instance, **kwargs):
user = cache.get('user')
in settings.py
TEMPLATE_CONTEXT_PROCESSORS = (
'web.context_processors.global_variables',
)

django bulk user import user django-import-export

i want use import export to bulk user import in django
i get a file include list of users then create users according to file rows
i try implement before_import like this
from import_export import resources
class UserResource(resources.ModelResource):
def before_import(self,dataset, dry_run, **kwargs):
#dataset is tablib.Dataset()
for i in dataset:
i[1]=make_password(i[1])
return super(UserResource, self).before_import(dataset, dry_run, **kwargs)
but it return tuple' object does not support item assignment
For Passwords you could write your own password widget, which turns the plain password into a hash. Like this (untested):
class PassWidget(Widget):
def clean(self, value):
if self.is_empty(value):
return None
return make_password(value)
def render(self, value):
return force_text(value)
I hope you are trying to hash your password before importing.
it helps you:
from import_export import resources, fields
from import_export.admin import ImportExportModelAdmin
from django.contrib.auth.hashers import make_password
class UserResource(resources.ModelResource):
groups = fields.Field(
column_name='group_name',
attribute='groups',
widget=ManyToManyWidget(Group, ',','name')
)
def before_import_row(self,row, **kwargs):
value = row['password']
row['password'] = make_password(value)
class Meta:
model = User

Django Admin linking to related objects

My app has users who create pages. In the Page screen of the admin, I'd like to list the User who created the page, and in that list, I'd like the username to have a link that goes to the user page in admin (not the Page).
class PageAdmin(admin.ModelAdmin):
list_display = ('name', 'user', )
list_display_links = ('name','user',)
admin.site.register(Page, PageAdmin)
I was hoping that by making it a link in the list_display it would default to link to the actual user object, but it still goes to Page.
I'm sure I'm missing something simple here.
Modifying your model isn't necessary, and it's actually a bad practice (adding admin-specific view-logic into your models? Yuck!) It may not even be possible in some scenarios.
Luckily, it can all be achieved from the ModelAdmin class:
from django.urls import reverse
from django.utils.safestring import mark_safe
class PageAdmin(admin.ModelAdmin):
# Add it to the list view:
list_display = ('name', 'user_link', )
# Add it to the details view:
readonly_fields = ('user_link',)
def user_link(self, obj):
return mark_safe('{}'.format(
reverse("admin:auth_user_change", args=(obj.user.pk,)),
obj.user.email
))
user_link.short_description = 'user'
admin.site.register(Page, PageAdmin)
Edit 2016-01-17:
Updated answer to use make_safe, since allow_tags is now deprecated.
Edit 2019-06-14:
Updated answer to use django.urls, since as of Django 1.10 django.core.urls has been deprecated.
Add this to your model:
def user_link(self):
return '%s' % (reverse("admin:auth_user_change", args=(self.user.id,)) , escape(self.user))
user_link.allow_tags = True
user_link.short_description = "User"
You might also need to add the following to the top of models.py:
from django.template.defaultfilters import escape
from django.core.urls import reverse
In admin.py, in list_display, add user_link:
list_display = ('name', 'user_link', )
No need for list_display_links.
You need to use format_html for modern versions of django
#admin.register(models.Foo)
class FooAdmin(admin.ModelAdmin):
list_display = ('ts', 'bar_link',)
def bar_link(self, item):
from django.shortcuts import resolve_url
from django.contrib.admin.templatetags.admin_urls import admin_urlname
url = resolve_url(admin_urlname(models.Bar._meta, 'change'), item.bar.id)
return format_html(
'{name}'.format(url=url, name=str(item.bar))
)
I ended up with a simple helper:
from django.shortcuts import resolve_url
from django.utils.safestring import SafeText
from django.contrib.admin.templatetags.admin_urls import admin_urlname
from django.utils.html import format_html
def model_admin_url(obj: Model, name: str = None) -> str:
url = resolve_url(admin_urlname(obj._meta, SafeText("change")), obj.pk)
return format_html('{}', url, name or str(obj))
Then you can use the helper in your model-admin:
class MyAdmin(admin.ModelAdmin):
readonly_field = ["my_link"]
def my_link(self, obj):
return model_admin_url(obj.my_foreign_key)
I needed this for a lot of my admin pages, so I created a mixin for it that handles different use cases:
pip install django-admin-relation-links
Then:
from django.contrib import admin
from django_admin_relation_links import AdminChangeLinksMixin
#admin.register(Group)
class MyModelAdmin(AdminChangeLinksMixin, admin.ModelAdmin):
# ...
change_links = ['field_name']
See the GitHub page for more info. Try it out and let me know how it works out!
https://github.com/gitaarik/django-admin-relation-links
I decided to make a simple admin mixin that looks like this (see docstring for usage):
from django.contrib.contenttypes.models import ContentType
from django.utils.html import format_html
from rest_framework.reverse import reverse
class RelatedObjectLinkMixin(object):
"""
Generate links to related links. Add this mixin to a Django admin model. Add a 'link_fields' attribute to the admin
containing a list of related model fields and then add the attribute name with a '_link' suffix to the
list_display attribute. For Example a Student model with a 'teacher' attribute would have an Admin class like this:
class StudentAdmin(RelatedObjectLinkMixin, ...):
link_fields = ['teacher']
list_display = [
...
'teacher_link'
...
]
"""
link_fields = []
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.link_fields:
for field_name in self.link_fields:
func_name = field_name + '_link'
setattr(self, func_name, self._generate_link_func(field_name))
def _generate_link_func(self, field_name):
def _func(obj, *args, **kwargs):
related_obj = getattr(obj, field_name)
if related_obj:
content_type = ContentType.objects.get_for_model(related_obj.__class__)
url_name = 'admin:%s_%s_change' % (content_type.app_label, content_type.model)
url = reverse(url_name, args=[related_obj.pk])
return format_html('{}', url, str(related_obj))
else:
return None
return _func
If anyone is trying to do this with inline admin, consider a property called show_change_link since Django 1.8.
Your code could then look like this:
class QuestionInline(admin.TabularInline):
model = Question
extra = 1
show_change_link = True
class TestAdmin(admin.ModelAdmin):
inlines = (QuestionInline,)
admin.site.register(Test, TestAdmin)
This will add a change/update link for each foreign key relationship in the admin's inline section.

Django - send email on model change

I want to send an email when a specific field is changed in a model. Is it possible? Here is what I am looking for. I have a profile model that includes a BooleanField that when the administrator selects to be true I want to send user an email. I know I could put it in a "def save(self):" but, that fires off an email anytime the model is changed and the field is true. Is there a way to have it only email if the field was changed from False to True?
save method is a perfectly good place for what you want to do:
def save(self):
if self.id:
old_foo = Foo.objects.get(pk=self.id)
if old_foo.YourBooleanField == False and self.YourBooleanField == True:
send_email()
super(Foo, self).save()
You can use django-model-changes to do this without an additional database lookup:
from django.db import models
from django.dispatch import receiver
from django_model_changes import ChangesMixin
class MyModel(ChangesMixin, models.Model):
flag = models.BooleanField()
#receiver(pre_save, sender=MyModel)
def send_email_if_flag_enabled(sender, instance, **kwargs):
if instance.previous_instance().flag == False and instance.flag == True:
# send email
Something like this could help and only sends an email when change from false to true
#models.py
from django.contrib.auth.models import User
from django.db.models import signals
from django.db import models
from django.dispatch import receiver
from django.db.models.signals import pre_save
from django.conf import settings
from django.core.mail import send_mail
#signal used for is_active=False to is_active=True
#receiver(pre_save, sender=User, dispatch_uid='active')
def active(sender, instance, **kwargs):
if instance.is_active and User.objects.filter(pk=instance.pk, is_active=False).exists():
subject = 'Active account'
mesagge = '%s your account is now active' %(instance.username)
from_email = settings.EMAIL_HOST_USER
send_mail(subject, mesagge, from_email, [instance.email], fail_silently=False)
Use hook a function with your models post_save using django signals (http://docs.djangoproject.com/en/dev/ref/signals/#django.db.models.signals.post_save)
In that function use standard django mailing: http://docs.djangoproject.com/en/dev/topics/email/