I have some problems with getting correct model names when try to get it via user = request.user, it shows every user like User, even if it is Student.
Okay, more details here:
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
class UserManager(BaseUserManager):
pass
class User(AbstractBaseUser):
objects = UserManager()
pass
class Student(User):
pass
When in view code I try to get user = request.user, I need to get user model name to show them different views. I tried it with django-polymorphic, it works good for other objects but when I try to inherit from it everything goes down, I do not understand how to do it.
i tried something like this:
class User(AbstractBaseUser, PolymorphicModel):
pass
and in manager:
class UserManager(BaseUserManager, PolymorphicManager):
pass
but it doesnt work, could someone please show me how to configure them together or say better way to get correct model names from request.user
I found some decicion, here is code:
class InheritanceMetaclass(ModelBase):
def __call__(cls, *args, **kwargs):
obj = super(InheritanceMetaclass, cls).__call__(*args, **kwargs)
return obj.get_object()
class User(AbstractBaseUser):
__metaclass__ = InheritanceMetaclass
object_class = models.CharField(max_length=20)
objects = BaseUserManager()
def get_object(self):
if not self.object_class or self._meta.model_name == self.object_class:
return self
else:
return getattr(self, self.object_class)
def save(self, *args, **kwargs):
if not self.object_class:
self.object_class = self._meta.model_name
super(User, self).save( *args, **kwargs)
# inherits
class User1(User):
text1 = models.CharField(max_length=20)
text2 = models.CharField(max_length=20)
class User2(User):
text3 = models.CharField(max_length=20)
text4 = models.CharField(max_length=20)
It seams it works but I am sure should be better way to do it.
now you can get user class name in object_class attribute, user.object_class
1 more way here:
def get_class_name(instance):
return instance.__class__.__name__
and then:
classname = get_class_name(user)
But I do not think it is good ways anyways...
Related
I've just setup inheritance on my models, this makes much better sense for my project - everything inherits from Item class, with common elements. Then additional fields are added to child classes - Video, Podcast, Article etc...
Previously, upon Item.save() I had some pre_save and post_save signals setup which parsed/performed various tasks on my Item model objects.
#receiver(pre_save, sender=Item)
def some_function(sender, instance, *args, **kwargs):
print("I am working")
However, now that my objects are all subclassing from Item, those signals don't seem to be firing.
>Article.objects.get_or_create(title='some title')
#receiver(pre_save, sender=Article)
def some_function(sender, instance, *args, **kwargs):
print("I am not doing anything")
models.py
class Item(models.Model, AdminVideoMixin):
title = models.TextField(max_length=2000)
slug = models.SlugField(max_length=500, default='')
link = models.URLField(max_length=200)
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(get_random_string(length=5,allowed_chars='1234567890') + '-' + self.title)
super().save(*args, **kwargs)
def get_absolute_url(self):
return reverse('curate:item_detail',args=[self.slug])
def __str__(self):
return self.title
class Video(Item):
video_embed = EmbedVideoField(max_length=300)
channel = models.TextField(max_length=500, blank=True
def __str__(self):
return self.title.upper()
class Article(Item):
authors = models.CharField(max_length=10000)
movies = models.TextField(max_length=10000)
def __str__(self):
return self.title.upper()
class Podcast(Item):
authors = models.CharField(max_length=10000)
itune_image = models.CharField(max_length=10000)
owner_name = models.CharField(max_length=10000)
def __str__(self):
return self.title.upper()
class Episode(Item):
authors = models.CharField(max_length=10000)
itune_image = models.CharField(max_length=10000)
owner_name = models.CharField(max_length=10000)
enclosure_url = models.URLField(max_length=2000)
owner = models.ForeignKey(Podcast, on_delete=models.CASCADE,max_length=2000)
class Meta:
pass
def __str__(self):
return self.title.upper()
I even stacked up the recievers as per this thread in case it's supposed to be triggered by the parent class as well as child class, but still it didn't save.
#receiver(post_save, sender=Video)
#receiver(post_save, sender=Item)
#receiver(post_save, sender=Article)
def some_function(sender, instance, *args, **kwargs):
print('This still didn't fire')
Also tried to use post_save.connect(some_function, sender=Article) and I couldn't get signals to fire. I'm creating the objects with get_or_create(attributes)
Will I have to manually write something to go in afterwards and perform pre-post/saves or am I doing something wrong? I saw a solution but this suggest sending the signal to all child classes - no matter which - but this isn't what I want. I'd like to set model specific post_save / pre_save signals rather than a blanket rule.
I think had a similar problem when I wanted to add a pre_save signal for all my sub model calsses but I wanted to implement at one place only. The solution was possible with python 3.6+ becuse it require __init_subclass__ magic method.
The problem that the child models sub classes are not automatically registered for the signal. So instead of manually register the signals you could register the signal for the child models in the init_subclass function
The code example below shows the jist of the implementation:
Class BaseModel:
common_field = models.CharField(...)
#classmethod
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
# automatically bind the pre_save for the sub model
pre_save.connect(generate_common_field, cls)
def generate_common_field:
pass
Class SubModel(BaseModel):
pass
I have a model that requires some post-processing (I generate an MD5 of the body field).
models.py
class MyModel(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
body = models.TextField()
md5 = models.CharField(max_length=32)
...
def save(self, *args, **kwargs):
if self.pk is None: # Only for create (edit disabled)
self.md5 = get_md5(self.body)
super(MyModel, self).save(*args, **kwargs)
The problem is that the final block won't execute because I don't see a way to check if the instance is new or not: self.pk is never None because a UUID is populated before saving.
I'd like to know what the best practice is for handling this.
Thanks in advance.
Update:
The only solution I can think of is to call the database directly and:
Check if the id exists
Compare the modified and created fields to tell if it's an edit
EDIT
self.pk is never None because a UUID is populated before saving.
Instead of setting a default for id, use a method to set id for the new instance.
class MyModel(...):
id = models.UUIDField(primary_key=True, default=None,...)
def set_pk(self):
self.pk = uuid.uuid4()
def save(self, *args, **kwargs):
if self.pk is None:
self.set_pk()
self.md5 = get_md5(self.body)
super(MyModel, self).save(*args, **kwargs)
Looks like the cleanest approach to this is to make sure that all your models have a created date on them by inheriting from an Abstract model, then you simply check if created has a value:
models.py
class BaseModel(models.Model):
"""
Base model which all other models can inherit from.
"""
id = fields.CustomUUIDField(primary_key=True, default=uuid.uuid4, editable=False)
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
class Meta:
# Abstract models are not created in the DB
abstract = True
class MyModel(BaseModel):
my_field = models.CharField(max_length=50)
def save(self, *args, **kwargs):
if self.created:
# Do stuff
pass
super(MyModel, self).save(*args, **kwargs)
I just got the same issue in my project and found out that you can check the internal state of the model's instance:
def save(self, *args, **kwargs):
if self._state.adding: # Only for create (edit disabled)
self.md5 = get_md5(self.body)
super(MyModel, self).save(*args, **kwargs)
But this solution relies on internal implementation and may stop working after Django is updated
As I've answered here as well, the cleanest solution I've found that doesn't require any additional datetime fields or similar tinkering is to plug into the Django's post_save signal. Add this to your models.py:
from django.db.models.signals import post_save
from django.dispatch import receiver
#receiver(post_save, sender=MyModel)
def mymodel_saved(sender, instance, created, **kwargs):
if created:
# do extra work on your instance
self.md5 = get_md5(self.body)
This callback will block the save method, so you can do things like trigger notifications or update the model further before your response is sent back over the wire, whether you're using forms or the Django REST framework for AJAX calls. Of course, use responsibly and offload heavy tasks to a job queue instead of keeping your users waiting :)
I was trying to create a django form and one of my field contain a ModelChoiceField
class FooForm(forms.Form):
person = forms.ModelChoiceField(queryset=Person.objects.filter(is_active=True).order_by('id'), required=False)
age = forms.IntegerField(min_value=18, max_value=99, required=False)
When I try the code above what it return as an html ouput is
<option value="1">Person object</option>
on my Person Model I have the fields "id, fname, lname, is_active" . Is it possible to specify that my dropdown option will use "id" as the value and "lname" as the label? The expected html
should be
<option value="1">My Last Name</option>
Thanks in advance!
You can just add a call to label_from_instance in the init of Form ie
by adding something like
class TestForm(ModelForm):
def __init__(self, *args, **kwargs):
super(TestForm, self).__init__(*args, **kwargs)
self.fields['field_name'].label_from_instance = self.label_from_instance
#staticmethod
def label_from_instance(obj):
return "My Field name %s" % obj.name
From the Django docs:
https://docs.djangoproject.com/en/dev/ref/forms/fields/#django.forms.ModelChoiceField
The __unicode__ (__str__ on Python 3) method of the model will be
called to generate string representations of the objects for use in
the field’s choices; to provide customized representations, subclass
ModelChoiceField and override label_from_instance. This method will
receive a model object, and should return a string suitable for
representing it. For example:
from django.forms import ModelChoiceField
class MyModelChoiceField(ModelChoiceField):
def label_from_instance(self, obj):
return "My Object #%i" % obj.id
So, you can do that, or override __str__ on your model class to return the last name.
In your Person model add:
def __unicode__(self):
return u'{0}'.format(self.lname)
If you are using Python 3, then define __str__ instead of __unicode__.
def __str__(self):
return u'{0}'.format(self.lname)
You can overwrite label_from_instance method of the ModelChoiceField instance to your custom method. You can do it inside the __init__ method of the form
class FooForm(forms.Form):
person = forms.ModelChoiceField(queryset=Person.objects.filter(is_active=True).order_by('id'), required=False)
age = forms.IntegerField(min_value=18, max_value=99, required=False)
def __init__(self, *args, **kwargs):
super(FooForm, self).__init__(*args, **kwargs)
self.fields['person'].label_from_instance = lambda instance: instance.name
to chose/change the value you can use "to_field_name" options ,
and to change the label of option you can overwrite the "label_from_instance" function inside ModelChoiceField class,
and here is a simple example ,
forms.py:
from django import forms
from .models import Group
from django.forms import ModelChoiceField
class MyModelChoiceField(ModelChoiceField):
def label_from_instance(self, obj):
return f"My Object {obj.group_name}"
class MyPureDjangoForm(forms.Form):
group = MyModelChoiceField(queryset=Group.objects.all(),
widget=forms.Select(attrs={
'class': 'form-control'
}),
to_field_name='id',
)
for more information's kindly visit the following URL :
https://docs.djangoproject.com/en/3.2/ref/forms/fields/#django.forms.ModelChoiceField
i hope this helpful .
Similar to Thomas's answer, I recommend setting the label_from_instance method reference when creating the field. However, as I almost always want the model to have the same value for select field drop downs, I just define a get_select_field_label() method on the Model itself. This is especially useful when I want different select labels and __str__() representations. Heres a minimal example:
from django.db import models
from django import forms
class Book(models.Model):
author = models.ForeignKey("Author", on_delete=models.CASCADE)
title = models.TextField()
def __str__(self):
return f"{self.title}, by {self.author.full_name}"
def get_select_field_label(self):
return f"{self.title}"
class BookForm(forms.Form):
title = forms.ModelChoiceField(queryset=Book.objects.all(), widget=forms.Select())
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['title'].label_from_instance = Book.get_select_field_label
The particular case I have is like this:
I have a Transaction model, with fields: from, to (both are ForeignKeys to auth.User model) and amount. In my form, I'd like to present the user 2 fields to fill in: amount and from (to will be automaticly set to current user in a view function).
Default widget to present a ForeignKey is a select-box. But what I want to get there, is limit the choices to the user.peers queryset members only (so people can only register transactions with their peers and don't get flooded with all system users).
I tried to change the ModelForm to something like this:
class AddTransaction(forms.ModelForm):
from = ModelChoiceField(user.peers)
amount = forms.CharField(label = 'How much?')
class Meta:
model = models.Transaction
But it seems I have to pass the queryset of choices for ModelChoiceField right here - where I don't have an access to the web request.user object.
How can I limit the choices in a form to the user-dependent ones?
Use the following method (hopefully it's clear enough):
class BackupForm(ModelForm):
"""Form for adding and editing backups."""
def __init__(self, *args, **kwargs):
systemid = kwargs.pop('systemid')
super(BackupForm, self).__init__(*args, **kwargs)
self.fields['units'] = forms.ModelMultipleChoiceField(
required=False,
queryset=Unit.objects.filter(system__id=systemid),
widget=forms.SelectMultiple(attrs={'title': _("Add unit")}))
class Meta:
model = Backup
exclude = ('system',)
Create forms like this:
form_backup = BackupForm(request.POST,
instance=Backup,
systemid=system.id)
form_backup = BackupForm(initial=form_backup_defaults,
systemid=system.id)
Hope that helps! Let me know if you need me to explain more in depth.
I ran into this problem as well, and this was my solution:
class ChangeEmailForm(forms.ModelForm):
def __init__(self, user, *args, **kwargs):
self.user = user
super(ChangeEmailForm, self).__init__(*args, **kwargs)
self.fields['email'].initial = user.email
class Meta:
model = User
fields = ('email',)
def save(self, commit=True):
self.user.email = self.cleaned_data['email']
if commit:
self.user.save()
return self.user
Pass the user into the __init__ of the form, and then call super(…). Then set self.fields['from'].queryset to user.peers
I'm configuring the admin site of my new project, and I have a little doubt on how should I do for, on hitting 'Save' when adding data through the admin site, everything is converted to upper case...
Edit: Ok I know the .upper property, and I I did a view, I would know how to do it, but I'm wondering if there is any property available for the field configuration on the admin site :P
If your goal is to only have things converted to upper case when saving in the admin section, you'll want to create a form with custom validation to make the case change:
class MyArticleAdminForm(forms.ModelForm):
class Meta:
model = Article
def clean_name(self):
return self.cleaned_data["name"].upper()
If your goal is to always have the value in uppercase, then you should override save in the model field:
class Blog(models.Model):
name = models.CharField(max_length=100)
def save(self, force_insert=False, force_update=False):
self.name = self.name.upper()
super(Blog, self).save(force_insert, force_update)
Updated example from documentation suggests using args, kwargs to pass through as:
Django will, from time to time, extend the capabilities of built-in
model methods, adding new arguments. If you use *args, **kwargs in
your method definitions, you are guaranteed that your code will
automatically support those arguments when they are added.
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
def save(self, *args, **kwargs):
do_something()
super(Blog, self).save( *args, **kwargs) # Call the "real" save() method.
do_something_else()
you have to override save(). An example from the documentation:
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
def save(self, force_insert=False, force_update=False):
do_something()
super(Blog, self).save(force_insert, force_update) # Call the "real" save() method.
do_something_else()