Django Admin - Change field model and automatically update another model - django

Well, I have a VagasUsuarios model and a Questionario model. I would like that when I updated the Questionario.pontuacao_questionario field via django admin, my other VagaUsuarios.pontuacao_vaga field would be updated as well. Is there a way to do this?
thanks for listening =)
My Models:
class Questionario(models.Model):
usuario = models.ForeignKey(Contas, on_delete=models.CASCADE)
[...]
pontuacao_questionario = models.DecimalField(max_digits=5, decimal_places=2, null=True, blank=True,verbose_name="Pontuacao do QuestionĂ¡rio")
class VagasUsuarios(models.Model):
usuario = models.ForeignKey(Contas, on_delete=models.CASCADE)
[...]
pontuacao_vaga = models.DecimalField(max_digits=5, decimal_places=2, verbose_name="Pontuacao da Vaga")

You could do this with signals.
Example:
from django.db.models.signals import post_save
from django.dispatch import receiver
from myapp.models import Questionario, VagasUsuarios
#receiver(post_save, sender=Questionario)
def my_handler(sender, instance, **kwargs):
obj = VagasUsuarios.objects.get(...)
obj.pontuacao_vaga = instance.pontuacao_questionario
obj.save()
Another is to override the save() (or rather clean()) method of your model and when it gets updated to fetch all the relevant VagasUsuarios-objects you want to update and update them.
Note on clean(): You got to call the clean-method yourself unless you are using the Django admin.

Related

How to refer to django foreign key?

I used to use a OneToOneField relation to the User model, but I had to switch to foreign key (because I want to store multiple dates for 1 user). And now I can't seem to figure out how to refer to my data inside my view.
view.py
def get_data(request, *args,**kwargs):
data = {
'weight': request.user.user_profile.weight,
'goal': request.user.user_profile.goal,
'date': request.user.user_profile.created_at,
}
return JsonResponse(data)
models.py
from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
from datetime import date
# Create your models here.
class Profile(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='user_profile')
weight = models.FloatField(max_length=20, blank=True, null=True)
height = models.FloatField(max_length=20, blank=True, null=True)
goal = models.FloatField(max_length=20, blank=True, null=True)
created_at = models.DateField(auto_now_add=True)
def __str__(self):
return self.user.username
#receiver(post_save, sender=User)
def save_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
I think you should keep OneToOne field. If you want multiple dates you can create ForeignKey for the dates.
If you still want ForeignKey Profile-User, you can try to filter the Profile model, to get the particular profile you need, by username, date etc.:
profile = Profile.objects.get(user=request.user, created_at=request.user.date_joined)
data = {
'weight': profile.weight,
'goal': profile.goal,
'date': profile.created_at,
}

Django: 'User' object has no attribute 'perfil_de_usuario'

I'm trying to make a page that, after the user is logued in, shows a list of diferent actions.
The proble is that, when the user is succesfully authenticated, the resulting page is this error:
AttributeError at /iniciar_sesion/
'User' object has no attribute 'perfil_de_usuario'
Request Method: POST
Request URL: http://127.0.0.1:8000/iniciar_sesion/
Django Version: 3.0.3
Exception Type: AttributeError
Exception Value:
'User' object has no attribute 'perfil_de_usuario'
Exception Location: /home/jenifer/Documentos/qbit/mysite4/usuarios/models.py in guardar_usuario_perfil, line 25
The model is as follows:
from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
class Perfil_de_Usuario(models.Model):
idusuario = models.AutoField(db_column='idUsuario', primary_key=True)
nombres = models.CharField(max_length=45, blank=True, null=True)
apellidos = models.CharField(max_length=45, blank=True, null=True)
clave = models.CharField(max_length=45, blank=True, null=True)
email = models.CharField(max_length=45, blank=True, null=True)
web = models.URLField(blank=True)
class Meta:
managed = False
db_table = 'Usuario'
#receiver(post_save, sender=User)
def crear_usuario_perfil(sender, instance, created, **kwargs):
if created:
perfil_de_usuario.objects.create(usuario=instance)
#receiver(post_save, sender=User)
def guardar_usuario_perfil(sender, instance, **kwargs):
instance.perfil_de_usuario.save()
For what the error says, the problem is with guardar_usuario_perfil, but I'm not getting how to modify it for this thing to work.
I know there are similar posts and I've tried different solutions like rename instance.perfil_de_usuario.save() part but the result is the same.
If somebody can help me I will apreciate it very much
You should use a OneToOneField so that Django can add a reverse relation on the User model
class Perfil_de_Usuario(models.Model):
idusuario = models.OneToOne(settings.AUTH_USER_MODEL, related_name="perfil_de_usuario")
....
The related_name="perfil_de_usuario" is what Django will add to the User model so that you can do what you were trying to do on the signals.
Also, note I didn't use the user model directly in the OneToOneField but used settings.AUTH_USER_MODEL instead as it is the recommended way.
Here is the Django documentation on extending the user model

How to make a field read-only inside a CreateView

I am a Django beginner and I am trying to make read-only a 'price' field for an order. I think, based on what I have understood, this cannot be done inside the model itself, but rather inside a form.
Since I am using a CreateView generic view, I thought this could have been done by setting the attribute disabled equal to True, as said here.
so what I have done is, in views.py
from django.shortcuts import render
from django.views.generic import CreateView
from .models import Order
from django import forms
# Create your views here.
class CreateOrderView(CreateView):
model = Order
template_name = 'home.html'
meal_price = forms.DecimalField(disabled=True)
fields = [
'meal_name',
'meal_price',
'restaurant',
'customer',
]
But this doesn't work.
Here is my models.py
from django.db import models
from restaurant.models import Restaurant
from account.models import Customer
# Create your models here.
class Order(models.Model):
meal_name = models.CharField(max_length=255)
meal_price = models.DecimalField(max_digits=5, decimal_places=2)
restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE, default=None)
customer = models.ForeignKey(Customer, on_delete=models.CASCADE, default=None)
Can anybody give me a hint?
Please consider that I am still learning so I would prefer coded answers to descriptive ones.
Thank you in advance
Ok, thanks to dirkgroten, I have worked out the answer.
Basically what is needed (in my case) is:
an Order model in models.py
from django.db import models
from restaurant.models import Restaurant
from account.models import Customer
# Create your models here.
class Order(models.Model):
meal_name = models.CharField(max_length=255)
meal_price = models.DecimalField(max_digits=5, decimal_places=2)
restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE, default=None)
customer = models.ForeignKey(Customer, on_delete=models.CASCADE, default=None)
an OrderForm(ModelForm) in forms.py that modifies the price field setting the disabled attribute to true
from django.forms import ModelForm
from .models import Order
from django import forms
class OrderForm(ModelForm):
meal_price = forms.DecimalField(max_digits=5, decimal_places=2, disabled=True)
class Meta:
model = Order
fields = [
'meal_name',
'meal_price',
'restaurant',
'customer',
]
an OrderView(CreateView) in views.py
from django.shortcuts import render
from django.views.generic import CreateView
from .forms import OrderForm
# Create your views here.
class OrderView(CreateView):
form_class = OrderForm
template_name = 'home.html'
I have no experience with Django's CreateView but from what I read it works similar to a separate form. You could try something like this:
class CreateOrderView(CreateView):
model = Order
template_name = 'home.html'
fields = [
'meal_name',
'meal_price',
'restaurant',
'customer',
]
def __init__(self, *args, **kwargs):
super(CreateOrderView, self).__init__(*args, **kwargs)
self.fields['meal_price'].widget.attrs['disabled'] = True
From my experience, the disabled attribute will be good for security reasons as far as protecting against the user editing the HTML and changing the value. However you won't be able to access this value when passed into a clean method. If you need to perform actions on the value you should change 'disabled' to 'readonly', but you won't have the same data protection that disabled offers.

Auto Populate Slug field django

I have a model defined and over 100+ entries of data in my DB. I would like to auto populate a slug field and see it show up in the admin, since adding new entries for 100+ fields is not something I would like to do.
AutoSlug() field doesnt seem to be working when I add it to my model and make the migrations, prepopulated_fields = {'slug': ('brand_name',)} does not work using it within my admin.py and as well I have tried to add the default field on the slug as my desired field name within the Model but to no avail the solution didnt work.
Is their any other suggestions on how to get the slug filed pre-populated?
class Brand(models.Model):
brand_name = models.CharField(unique=True, max_length=100, blank=True, default="", verbose_name=_('Brand Name'))
slug = models.SlugField(max_length=255, verbose_name=_('Brand Slug'), default=brand_name)
You can try adding a save method to the Brand class.
from django.utils.text import slugify
class Brand(models.Model):
...
def save(self, *args, **kwargs):
self.slug = slugify(self.brand_name)
super(Brand, self).save(*args, **kwargs)
then run:
python manage.py shell
>>>from app.models import Brand
>>>brands = Brands.objects.all()
>>>for brand in brands:
>>> brand.save()
Also, I would change brand_name var to just name.
I think it's better to add a pre_save signal on the Brand model.
from django.dispatch import receiver
from django.db.models import signals
from django.contrib.auth import get_user_model
from django.utils.text import slugify
#receiver(signals.pre_save, sender=<model name>)
def populate_slug(sender, instance, **kwargs):
instance.slug = slugify(instance.brand_name)```
I think I have an idea, which would do the job, however I am not sure if that would be the best way to do it.
I would use slugify function. I would create the view which - after it was called - would get all model's objects, iterate over them and populate each model's slug field using django.utils.text.slugify function with model's brand_name as a value.

Django accessing ManyToMany fields from post_save signal

I have a Django model and I want to modify the object permissions on or just after save. I have tried a few solutions and the post_save signal seemed the best candidate for what I want to do:
class Project(models.Model):
title = models.CharField(max_length=755, default='default')
assigned_to = models.ManyToManyField(
User, default=None, blank=True, null=True
)
created_by = models.ForeignKey(
User,
related_name="%(app_label)s_%(class)s_related"
)
#receiver(post_save, sender=Project)
def assign_project_perms(sender, instance, **kwargs):
print("instance title: "+str(instance.title))
print("instance assigned_to: "+str(instance.assigned_to.all()))
In this case, when a Project is created, the signal fires and I see the title, but an empty list for the assigned_to field.
How can I access the saved assigned_to data following save?
You're not going to. M2Ms are saved after instances are saved and thus there won't be any record at all of the m2m updates. Further issues (even if you solve that) are that you're still in a transaction and querying the DB won't get you m2m with proper states anyways.
The solution is to hook into the m2m_changed signal instead of post_save.
https://docs.djangoproject.com/en/dev/ref/signals/#m2m-changed
Your sender then would be Project.assigned_to.through
If your m2m can be empty (blank=True) you are in a little trouble with m2m_changed, because m2m_changed doesn't fire if m2m wasn't set. You can solve this issue by using post_save and m2m_changed at the same time. But there is one big disadvantage with this method - your code will be executed twice if m2m field isn't empty.
So, you can use transaction's on_commit (Django 1.9+)
Django provides the on_commit() function to register callback
functions that should be executed after a transaction is successfully
committed.
from django.db import transaction
def on_transaction_commit(func):
def inner(*args, **kwargs):
transaction.on_commit(lambda: func(*args, **kwargs))
return inner
#receiver(post_save, sender=SomeModel)
#on_transaction_commit
def my_ultimate_func(sender, **kwargs):
# Do things here
Important note: this approach works only if your code calls save().
post_save signal doesn't fire at all in cases when you call only instance.m2m.add() or instance.m2m.set().
Use transaction on commit!
from django.db import transaction
#receiver(post_save, sender=Project)
def assign_project_perms(sender, instance, **kwargs):
transaction.on_commit(lambda: print("instance assigned_to: "+str(instance.assigned_to.all())))
here is an example about how to use signal with many to many field (post like and post comments models),
and in my example i have :
like model (Intermediary table for User and Post tables) : the user can add 1 record only in Intermediary table for each post , which means (unique_together = ['user_like', 'post_like']) for this type of many to many relations you can use 'm2m_changed' signals ,
comment model (Intermediary table for User and Post tables): the user can add many records in Intermediary table for each post , (without unique_together ), for this i just use 'post_save, post_delete' signals , but you can use also 'pre_save, pre_delete' if you like ,
and here is both usage example :
from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save, post_delete, m2m_changed
from django.dispatch import receiver
class Post(models.Model):
post_user = models.ForeignKey(User,related_name='post_user_related', on_delete=models.CASCADE)
post_title = models.CharField(max_length=100)
post_description = models.TextField()
post_image = models.ImageField(upload_to='post_dir', null=True, blank=True)
post_created_date = models.DateTimeField(auto_now_add=True)
post_updated_date = models.DateTimeField(auto_now=True)
post_comments = models.ManyToManyField(
User,
through="Comments",
related_name="post_comments"
)
p_like = models.ManyToManyField(
User, blank=True,
through="LikeIntermediary",
related_name="post_like_rel"
)
class LikeIntermediary(models.Model):
user_like = models.ForeignKey(User ,related_name="related_user_like", on_delete=models.CASCADE)
post_like = models.ForeignKey(Post ,related_name="related_post_like", on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f"{self.user_like} - {self.post_like} "
class Meta:
unique_together = ['user_like', 'post_like']
#receiver(m2m_changed, sender=LikeIntermediary)
def like_updated_channels(sender, instance, **kwargs):
print('this m2m_changed receiver is called, the instance is post id', instance.id)
class Comments(models.Model):
cmt_user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="related_comments_user")
cmt_post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name="related_comments_post")
cmt_created_date = models.DateTimeField(auto_now_add=True)
cmt_comment_body = models.TextField()
cmt_created = models.DateTimeField(auto_now_add=True)
cmt_updated = models.DateTimeField(auto_now=True)
#receiver(post_save, sender=Comments)
def comments_updated_channels(sender, instance, created, **kwargs):
print('this post_save receiver is called, the instance post id', instance.cmt_post.id)
#receiver(post_delete, sender=Comments)
def comments_deleted_channels(sender, instance, **kwargs):
print('this post_save receiver is called, the instance post id', instance.cmt_post.id)
notes :
the instance with 'm2m_changed' it is a post object .
the instance with 'post_save and post_delete' it is a comment object
this is just an example , and change it based on your case/requirements.
i hope this helpful