I am using Python 2.7 and Django 1.6.3
I want to define extra model field which is not actually in db table. I have a way which is defining a callable method with property annotation like;
class MyClass(models.Model):
my_field = models.CharField(max_length=50)
#property
def my_extra_field(self):
return self.my_field+'extra value'
This works fine to show it on admin change list pages. But the extra field is not on db level. It is being generated on programming level. Django asks it for every model object.
This cause me some troubles. My all admin change list pages have capability of exporting as excel or some other type. I am using admin query set to build that report. I have also jasper reports mechanism that works with SQL select queries. So, I, want to use the queryset to take this select query.
I think being able to define extra fields on db level is important for something. Not just for reason of mine. So, the question all about this.
Is there a way to define an extra custom fields on db level instead of programming level in Django.
Thank you!.
Edited
Adding it to admin list_filter is also another problem if it is not really a field. Django does not allow you to add it.
Could you create a new database field and then overwrite the save method to populate that field? I do that often to create a marked up version of a text field. For example:
class Dummmy(models.Model):
content = models.TextField()
content_html = models.TextField(editable=False, null=True)
def save(self, *args, **kwargs):
self.content_html = markdown(self.content)
super(Dummmy, self).save(*args, **kwargs)
So for you:
class MyClass(models.Model):
my_field = models.CharField(max_length=50)
my_extra_field = models.CharField(editable=False, null=True)
def save(self, *args, **kwargs):
self.my_extra_field = self.my_field + 'extra value'
super(MyClass, self).save(*args, **kwargs)
Related
I wish to do the following. When a new project is created , i want to notify everybody that was assigned to the project that there's a new project available.
Here is my simplified project model:
class SalesProject(models.Model):
sales_project_name = models.CharField(max_length=100)
userProfile = models.ManyToManyField('UserProfile', blank=True)
history = HistoricalRecords(excluded_fields=['version', 'project_status'])
def __str__(self):
return self.sales_project_name
When a project is being created, i will send out the following signal :
def CreateProjectNotification(sender, **kwargs):
if kwargs['action'] == "post_add" and kwargs["model"] == UserProfile:
for person in kwargs['instance'].userProfile.all():
#some check here to prevent me from creating a notification for the creator or the project
Notifications.objects.create(
target= person.user,
extra = 'Sales Project',
object_url = '/project/detail/' + str(kwargs['instance'].pk) + '/',
title = 'New Sales Project created')
The reason why im using the m2m_changed.connect instead of post_save is because i wish to access the M2M field , UserProfile to send out the notifications. Since the object would not be added to the through table at the point of creation , i can't use the post_save and instead i have to track the changes from the through table .
problem
With that said , this signal runs as long as the save() function is called and the model which changed was the UserProfile model .
This is problematic as for example , i don't wish to send this same message when a new user was added. Instead i wish to run a separate signal to handle for that.
Is there a way , other than using if else to distinguish between a creation of the object and adding of a related M2M object?
You should avoid using signals as much as possible as they are bad practise as explained here.
You should override the save() function of your SalesProject model and handle any logic in there. It is possible to check if a SalesProject is created or updated by using self._state.adding.
self._state.adding == True means the SalesProject is created
self._state.adding == False means the SalesProject is updated
Thus to achieve what you want:
class SalesProject(models.Model):
sales_project_name = models.CharField(max_length=100)
userProfile = models.ManyToManyField('UserProfile', blank=True)
history = HistoricalRecords(excluded_fields=['version', 'project_status'])
def save(self, *args, **kwargs):
if self._state.adding: # check if object is created and not updated
# your logic goes here i.e. create/delete (related) objects
return super(SalesProject, self).save(*args, **kwargs)
def __str__(self):
return self.sales_project_name
EDIT
Lets say you create an UserProfile object and want to add it as a relation to a SalesProject object by assigning it to its userProfile field. To get any values of a newly created UserProfile object, it has to be saved to the database first.
An example:
# SalesProject model class
def save(self, *args, **kwargs):
if self._state.adding:
user_profile = UserProfile.objects.create(someField=someValue)
self.userProfile.add(user_profile)
return super(SalesProject, self).save(*args, **kwargs)
The UserProfile.objects.create() will create a new UserProfile with the values that you assign to the fields and save it to the database.
self.userProfile.add(user_profile) adds the newly created and saved UserProfile as a relation to the ManyToManyField of the SalesProject that was just created.
Let me start by saying that I am working with a legacy database so avoiding the custom intermediate table is not an option.
I'm looking for an alternative way to get the limit_choices_to functionality as I need to only present the options flagged by the sample_option boolean in the Sampletype Model in my ModelForm:
class PlanetForm(ModelForm):
class Meta:
model = Planet
fields = ['name', 'samples']
Here is a simplified view of my models
class Planet(models.Model):
name= models.CharField(unique=True, max_length=256)
samples = models.ManyToManyField('Sampletype', through='Sample')
class Sample(models.Model):
planet = models.ForeignKey(Planet, models.DO_NOTHING)
sampletype = models.ForeignKey('Sampletype', models.DO_NOTHING)
class Sampletype(models.Model):
name = models.CharField(unique=True, max_length=256)
sample_option = models.BooleanField(default=True)
Sample is the intermediate table.
Normally, if the project had been started with Django in the first place, I could just define the ManyToManyField declaration as:
samples = models.ManyToManyField('Sampletype', limit_choices_to={'sample_option'=True})
But this is not an option.. So how do I get this functionality ?
Django clearly states in their documentation that:
limit_choices_to has no effect when used on a ManyToManyField with a
custom intermediate table specified using the through parameter.
But they offer no information on how to get that limit in place when you DO have a custom intermediate table.
I tried setting the limit_choices_to option on the ForeignKey in the Sample Model like so:
sampletype = models.ForeignKey('Sampletype', models.DO_NOTHING, limit_choices_to={'sample_option': True})
but that had no effect.
Strangely, I find no answer to this on the web and clearly other people must have to do this in their projects so I'm guessing the solution is really simple but I cannot figure it out.
Thanks in advance for any help or suggestions.
You could set the choices in the __init__ method of the form:
class PlanetForm(ModelForm):
class Meta:
model = Planet
fields = ['name', 'samples']
def __init__(self, *args, **kwargs):
super(PlanetForm, self).__init__(*args, **kwargs)
sample_choices = list(
Sampletype.objects.filter(sample_option=True).values_list('id', 'name')
)
# set these choices on the 'samples' field.
self.fields['samples'].choices = sample_choices
Example:
class MyUser(models.Model):
blocked_users = models.ManyToManyField("self", blank=True, null=True)
user = MyUser.object.get(pk=1)
user.blocked_users.add(user)
user.blocked_users.all()[0] == user # (!!!)
Can It be prevented on model/db level? Or we need just do check somewhere in app.
Looking at the Django docs for ManyToManyField arguments, it does not seem possible.
The closest argument to what you want is the limit_choices_to However, that only limits choices on ModelForms and admin (you can still save it like you did in your example), and there is currently no easy way to use it to limit based on another value (pk) in the current model.
If you want to prevent it from happening altogether, you'll have to resort to overriding the save method on the through model--something like:
class MyUser(models.Model):
blocked_users = models.ManyToManyField(..., through="BlockedUser")
class BlockedUser(models.Model):
user = models.ForeignKey(MyUser)
blocked = models.ForeignKey(MyUser)
def save(self, *args, **kwargs):
# Only allow this relationship to be created if
if self.user != self.blocked:
super(BlockedUser, self).save(*args, **kwargs)
You could of course also do this with signals.
What would be the best way to create a "Update history" field in my Django Model so that I can keep track of multiple update dates?
I can create a model with last_updated = models.DateTimeField() and then have my view save the datetime to it, but what if I want to have a history of when the user has updated their post, and not just the most recent save?
Should it be a ManyToManyField or a CharField instead?
It's shouldn't be a field at all. Instead create a model, that will reference your main model using a ForeignKey:
class YourModel(models.Model):
name = models.CharField(max_length=100)
class YourModelUpdateHistory(models.Model):
your_model = models.ForeignKey('YourModel')
updated = models.DateTimeField()
This way you can have multiple dates for every model, while keeping the database properly normalized. It will also allow you in the future to add additional fields with other information about each update (for example who updated the object).
You should create a new YourModelUpdateHistory object whenever you update a YourModel object. You can even set it up so this is done automatically, thanks to the save() method (which is called by Django every time you save an object):
from django.utils import timezone
class YourModel(models.Model):
name = models.CharField(max_length=100)
def save(self, *args, **kwargs):
super(YourModel, self).save(*args, **kwargs)
YourModelUpdateHistory.objects.create(your_model=self, updated=timezone.now())
I want to add few fields to every model in my django application. This time it's created_at, updated_at and notes. Duplicating code for every of 20+ models seems dumb. So, I decided to use abstract base class which would add these fields. The problem is that fields inherited from abstract base class come first in the field list in admin. Declaring field order for every ModelAdmin class is not an option, it's even more duplicate code than with manual field declaration.
In my final solution, I modified model constructor to reorder fields in _meta before creating new instance:
class MyModel(models.Model):
# Service fields
notes = my_fields.NotesField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
last_fields = ("notes", "created_at", "updated_at")
def __init__(self, *args, **kwargs):
new_order = [f.name for f in self._meta.fields]
for field in self.last_fields:
new_order.remove(field)
new_order.append(field)
self._meta._field_name_cache.sort(key=lambda x: new_order.index(x.name))
super(MyModel, self).__init__(*args, **kwargs)
class ModelA(MyModel):
field1 = models.CharField()
field2 = models.CharField()
#etc ...
It works as intended, but I'm wondering, is there a better way to acheive my goal?
I was having the very same problem, but I found these solutions to be problematic, so here's what I did:
class BaseAdmin(admin.ModelAdmin):
def get_fieldsets(self, request, obj = None):
res = super(BaseAdmin, self).get_fieldsets(request, obj)
# I only need to move one field; change the following
# line to account for more.
res[0][1]['fields'].append(res[0][1]['fields'].pop(0))
return res
Changing the fieldset in the admin makes more sense to me, than changing the fields in the model.
If you mainly need the ordering for Django's admin you could also create your "generic"-admin class via sub-classing Django's admin class. See http://docs.djangoproject.com/en/dev/intro/tutorial02/#customize-the-admin-form for customizing the display of fields in the admin.
You could overwrite the admin's __init__ to setup fields/fieldsets on creation of the admin instance as you wish. E.g. you could do something like:
class MyAdmin(admin.ModelAdmin):
def __init__(self, model, admin_site):
general_fields = ['notes', 'created_at', 'updated_at']
fields = [f.name for f in self.model._meta.fields if f.name not in general_fields]
self.fields = fields + general_fields
super(admin.ModelAdmin, self).__init__(model, admin_site)
Besides that i think it's not a good practice to modify the (private) _field_name_cache!
I ALSO didn't like the other solutions, so I instead just modified the migrations files directly.
Whenever you create a new table in models.py, you will have to run "python manage.py makemigrations" (I believe this in Django >= v1.7.5). Once you do this, open up the newly created migrations file in your_app_path/migrations/ directory and simply move the rows to the order you want them to be in. Then run "python manage.py migrate". Voila! By going into "python manage.py dbshell" you can see that the order of the columns is exactly how you wanted them!
Downside to this method: You have to do this manually for each table you create, but fortunately the overhead is minimal. And this can only be done when you're creating a new table, not to modify an existing one.