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())
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.
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.
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)
Is there an easy way to allow for required profile fields?
I am using userena in my current django project. I have a custom profile called UserProfile which has a start_year none-blank, non-null field as defined below.
class UserProfile(UserenaBaseProfile, PybbProfile):
user = models.OneToOneField(User, unique=True,
verbose_name=_('user'),
related_name='user_profile')
start_year = models.IntegerField(max_length=4)
I need this to be filled-in on signup. I created a SignupExtraForm as defined below, to override the default form.
class SignupFormExtra(SignupForm):
start_year = forms.IntegerField(label=_(u'Initiation Year'),
min_value=1800,
max_value=datetime.now().year,
required=True)
def save(self):
new_user = super(SignupFormExtra, self).save()
new_user_profile = new_user.get_profile()
new_user_profile.start_year = self.cleaned_data['start_year']
new_user_profile.save()
# Userena expects to get the new user from this form, so return the new
# user.
return new_user
When I attempt to add a new user thru the now modified form I get the below error:
profile_userprofile.start_year may not be NULL
With the stack trace pointing at new_user = super(SignupFormExtra, self).save()), in the code above.
I think this has to do with the user profile being created and saved before I am able to give it the required data from the form. Is there an easy way of supplying this data to the user_creation process, or delaying the creating of the user profile?
Thanks
Shon
UserProfile is created after the User is saved by a post_save signal. Even if you override it with your own signal, you won't have access to the form data from there.
The easiest solution is to just allow start_year to be NULL. It's not necessary to enforce this at the database level, and you can make the field required in all forms either way:
start_year = models.IntegerField(max_length=4, blank=False, null=True)
Your custom form already enforces that the field is required, so you're done.
UPDATE (from comment)
Custom form, yes, but you can still use a ModelForm:
class MyModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(MyModelForm, self).__init__(*args, **kwargs)
self.fields['start_year'].required = True
UPDATE (again)
Actually, I didn't think before my last update. I put blank=False on the start_year field in my example. That will force that the field is required in all ModelForms by default. You don't need a custom ModelForm at all. However, I left the previous update for posterity.
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.