django model not in urls.py - django

I am new to python and django and I want to know the best way to set things up.
I have a model called OauthProviders which I set up to encrypt some fields before save in the ModelViewSet (override perform_create method). I dont want to create routes (urls) for this model.
Now, if I want to access this model in the code (I can with OauthProvider.objects.all() of course), but I have a few questions:
how do I enter data to this model NOT in code? If I use the admin portal for it, it doesn't execute my custom perform_create method, so it gets added to the database in plain text
What is the best way to decrypt a message if I retrieve data?
EDIT:
I moved the logic from the ModelViewSet to the save() method on the model with the following code:
def save(self, *args, **kwargs):
self.credentials = encrypt_message(self.credentials, '<keyhere>')
return super().save(*args, **kwargs)
This seems to work. Is this good practice? How to do this with the get method?

A good way of doing this is using django signals.
Assuming you have:
class OauthProvider(models.Model):
pass # some fields here
In your case you can use either:
pre_save - code that will be executed before OauthProvider gets saved in the database.
post_save - code that will be executed after OauthProvider gets saved in the database.
It's up to you to decide when exactly you want to execute additional code.
Example with pre_save:
from django.db.models.signals import pre_save
from django.dispatch import receiver
class OauthProvider(models.Model):
pass # some fields here
#receiver(pre_save, sender=OauthProvider)
def oauthprovider_on_save(sender, instance, **kwargs):
instance.credentials = encrypt_message(self.credentials, '<keyhere>')
# no need to call instance.save() in pre_save signal
The pre_save signal will be executed:
If you call obj.save() programatically.
If you save the obj through django admin site.
If you create/update the obj within rest api

Related

django post_save signal and ManyToManyField (and Django Admin)

I have a problem with a post_save function. The function is correctly triggered but the instance doesn't contains the value insereted. I checked the function using ipdb and there is nothing wrong. Simply the ManyToManyField is empty.
The code:
#receiver(post_save, sender=Supplier)
def set_generic_locations(sender, instance, **kwargs):
""" Set the generic locations for the NEW created supplier.
"""
created = kwargs.get('created')
if created:
glocations = LocationAddress.get_generic_locations()
for location in glocations:
instance.locations.add(location)
instance.save()
The field used in the instance:
locations = models.ManyToManyField(LocationAddress, blank=True)​
I don't understand why, but the locations is always empty.
I use django 1.8.8
UPDATE
The problem is the django admin. I found an explanation here: http://timonweb.com/posts/many-to-many-field-save-method-and-the-django-admin/
The code that solve the problem in the django admin
def save_related(self, request, form, formsets, change):
super(SupplierAdmin, self).save_related(request, form, formsets, change)
form.instance.set_generic_locations()
ManyToManyFields work a little bit differently with signals because of the difference in database structures. Instead of using the post_save signal, you need to use the m2m_changed signal
For manytomanyfield, you have to save first your parent object () and then add your

Django call method after object save with new instance

I want to call a method after this object (in this example Question) is saved.
This is what I got:
class Question(models.Model):
...
def after_save(sender, instance, *args, **kwargs):
some_method(instance)
post_save.connect(Question.after_save, sender=Question)
It kind of works. But the problem is that the instance data is the old one (same as before the save). So some_method(instance) gets the old instance.
But I need the instance with the new data.
The method you call from post_save signal should be outside of the Model. You can put the method inside the models.py or in another file such as signals.py
class Question(models.Model):
...
def some_method(self):
return "hello"
def question_saved(sender, instance, *args, **kwargs):
instance.some_method()
post_save.connect(question_saved, sender=Question)
You can override save method, and call what ever you want after the object gets saved
Here is a related link: Django. Override save for model
What you want is something called signals: https://docs.djangoproject.com/en/3.2/topics/signals/.
Some operations send a signals before and after they're completed successfully. In the docs you can find a list of built-in signals. You can also create custom signals. For example assume that I want to do something specific each time a new user is created. Then my code would be something like this:
users/models.py
from django.contrib.auth.models import AbstractUser
from django.dispatch import receiver
from django.db.models.signals import post_save
class CustomUser(AbstractUser):
pass
###############
# Recieve the signals and get the signals that are sent
# after models are saved and whose sender is CustomUser
###############
#receiver(post_save, sender='CustomUser')
def do_something():
do_something_specific
You can also save your signals in a separate file called signals.py but you need to change your app's apps:
<appname>/apps.py
from django.apps import AppConfig
class AppnameConfig(AppConfig):
name = <appname>
def ready(self):
import <appname>.signals

Django: passing variables from pre_save to post_save signals

I use the pre_save and post_save signals to send analytics to Mixpanel. I prefer to keep this separated from my model's save method.
Is there a way to save the old values of an instance when the pre_save signal occurs, and then check the new values against them on post_save?
My code looks like this:
#receiver(pre_save, sender=Activity)
def send_user_profile_analytics(sender, **kwargs):
activity_completed_old_value = kwargs['instance'].is_completed
# store this value somewhere?
#receiver(post_save, sender=Activity)
def send_user_profile_analytics(sender, **kwargs):
if kwargs['instance'].is_completed != activity_completed_old_value:
# send analytics
For me it seems more robust to use post_save to send the analytics than pre_save, but at that point I can't see what has changed in the model instance. I would like to prevent using globals or implementing something in my model's save function.
You can store them as instance attributes.
#receiver(pre_save, sender=Activity)
def send_user_profile_analytics(sender, **kwargs):
instance = kwargs['instance']
instance._activity_completed_old_value = instance.is_completed
#receiver(post_save, sender=Activity)
def send_user_profile_analytics(sender, **kwargs):
instance = kwargs['instance']
if instance.is_completed != instance._activity_completed_old_value:
# send analytics
In this way you "send analytics" only if is_completed changes during save (that means that save doesn't just store the value but makes some further elaboration).
If you want to perform an action when a field is changed during instance life-time (that is from its creation till the save) you should store the initial value during post_init (and not pre_save).

django override User model

I'm trying to override the default User model in Django to add some logic into the save() method. I'm having a hard time trying to figure out out to go about this.
I'm using Django 1.1 if that helps.
I used post_save since i need to add the user into ldap.. I just added this into a models.py
from django.db import models
from django.contrib.auth.models import User
from django.db.models import signals
from django.dispatch import dispatcher
def user_post_save(sender, instance, **kwargs):
print "got here"
models.signals.post_save.connect(user_post_save, sender=User)
Don't. Instead catch the pre_save signal.
You'd better use a Proxy model, so to use the same table but overriding behavior.
This is the standard way to extend Django's own models, because they cannot be made abstract.
So declare your model as:
from django.contrib.auth.models import User
class CustomUser(User):
class Meta:
proxy = True
def save(self, *args, **kwargs):
# do anything you need before saving
super(CustomUser, self).save(*args, **kwargs)
# do anything you need after saving
and you are done.

How to restrict users voting on their own model

I am using django-voting as a voting application for two of my models. Those both models have fields "author".
How can I restrict a user from voting on a model that has this particular user set as it's author without modifying django-voting app?
Django middleware is the first thing that comes to my mind, but I don't understand it's "proces_view" function. If you think middleware is the right way could you please give an example of how to do it.
Add this code anywhere in your settings.py:
from voting.managers import VoteManager
def check_user(func):
def wrapper(self, obj, user, vote):
if obj.user != user:
return func(self, obj, user, vote)
else:
return None
# or raise some exception
return wrapper
VoteManager.record_vote = check_user(VoteManager.record_vote)
I didn't run this code, maybe it's incorrect, but I hope idea is clear
Rather than a middleware hack, why not reroute requests to that particular URI through another view? Then you can performs whatever logic you like, and subsequently call the original view if appropriate.
Another idea is to use the post_save signal
like so:
from django.db.models.signals import post_save
from voting.models import Vote
def check_user(sender, instance, **kwargs):
if instance.user == instance.object.user:
instance.delete()
# do some other stuff to tell the user it didn't work
post_save.connect(check_user, sender=Vote)
The benefit of doing this vs overriding VoteManager.record_vote is that it's less tightly coupled to the voting module, and if they make changes it's less likely to break your code
edit: as in Glader's answer, you need to make sure that all the objects you're voting on have a 'user' attribute.