Django: models last mod date and mod count - django

I have a django model called Blog.
I'd like to add a field to my current model that is for last_modified_date. I know how to set a default value, but I would like somehow for it to get automatically updated anytime I modify the blog entry via the admin interface.
Is there some way to force this value to the current time on each admin site save?
Also would there be some way to add a mod_count field and have it automatically calculated on each modify of the admin site blog entry?

Create a DateTimeField in your model. Have it update whenever it is saved. This requires you to use the auto_now_add option:
class DateTimeField([auto_now=False, auto_now_add=False, **options])
DateTimeField.auto_now_add¶
Automatically set the field to now every time the object is saved. Useful
for "last-modified" timestamps. Note
that the current date is always used;
it's not just a default value that you
can override.
It should look something like this:
class Message(models.Model):
message = models.TextField()
active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
Model field reference
For the second part, I think you have to overload
ModelAdmin.save_model(self, request, obj, form, change)
As James Bennett describes here. It will look something like this:
class EntryAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
if change:
obj.change_count += 1
obj.save()

The accepted answer is no longer correct.
For newer django versions, you will have to use the auto_now=True parameter rather than the auto_now_add=True, which will only set the field value when the object is initially created.
From the documentation:
DateField.auto_now_add¶
Automatically set the field to now when the
object is first created. Useful for creation of timestamps.
The desired functionality is now implemented by auto_now:
DateField.auto_now¶
Automatically set the field to now every time the
object is saved.
So to achieve self-updating timestamps a model should be created like this:
class Message(models.Model):
message = models.TextField()
active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now=True)
mod_count = models.IntegerField(default=0)
To increment mod_count everytime this model is modified overload the model's save() method:
def save(self, *args, **kwargs):
self.mod_count +=1
return super(Message,self).save(*args, **kwargs)

There's a number of ways you can increase the edit count each time it's saved.
The model itself has a save() method, and the admin model has a model_save() method.
So for example, let's say you wanted it to increment when it was edited with the admin tool....
models.py:
class MyModel(models.Model):
edit_count = models.IntegerField()
# ... rest of model code here...
admin.py:
class MyModelAdmin(admin.ModelAdmin)
def save_model(self, request, obj, form, change):
if not change:
obj.edit_count = 1
else:
obj.edit_count += 1
obj.save()
You could do similar code off of the model save() event as well.
Something else you may be interested in is django-command-extensions. It adds 2 fields which may be helpful to you:
CreationDateTimeField - DateTimeField that will automatically set it's date when the object is first saved to the database.
ModificationDateTimeField - DateTimeField that will automatically set it's date when an object is saved to the database.

You can also use a middleware solution found here: https://bitbucket.org/amenasse/django-current-user/src/7c3c90c8f5e854fedcb04479d912c1b9f6f2a5b9/current_user?at=default
settings.py
....
MIDDLEWARE_CLASSES = (
....
'current_user.middleware.CurrentUserMiddleware',
'current_user.middleware.CreateUserMiddleware',
)
....
INSTALLED_APPS = (
'current_user',
....
....
)
models.py
class ExampleModel(models.Model):
foo = models.IntegerField()
last_user = CurrentUserField(related_name="+")
created_by = CreateUserField(related_name="+")

Related

Setting Djando field default value based on another field

I am creating a Django model where:
expirationTimeStamp field's default value is based on creationTimeStamp
isLive boolean field value is based on expirationTimeStamp
I have written the following functions expirationTimeCalculation and postLiveStatus and assigned them as default values to the fields but I am getting error. I have also tried to assign to respective fields through property(function) yet I am still getting error.
One of the functionality that I need to implement is that user can send custom expirationTimeStamp as well that would override default value, therefore, I believe property(function) assignment is not ideal for expirationTimeStamp field.
Is there any other way that I can go about to set the expirationTimeStamp field value based on creationTimeStamp field?
Any feedback is appreciated!
class Posts(models.Model):
def expirationTimeCalculation(self):
EXPIRATION_DURATION = 86400 #time in seconds
expirationTime = self.creationTimestamp + timedelta(seconds = EXPIRATION_DURATION)
return expirationTime
def postLiveStatus(self):
return (self.expirationTimestamp > timezone.now)
message = models.TextField()
creationTimestamp = models.DateTimeField(default=timezone.now)
expirationTimestamp = models.DateTimeField(default=expirationTimeCalculation)
isLive = models.BooleanField(default=postLiveStatus)
Similar answered question. I am attaching the official documentation as well as the link to the answered question.
Models certainly do have a "self"! It's just that you're trying to define an attribute of a model class as being dependent upon a model instance; that's not possible, as the instance does not (and cannot) exist before your define the class and its attributes.
To get the effect you want, override the save() method of the model class. Make any changes you want to the instance necessary, then call the superclass's method to do the actual saving. Here's a quick example.
def save(self, *args, **kwargs):
if not self.subject_init:
self.subject_init = self.subject_initials()
super(Subject, self).save(*args, **kwargs)
Model Overriding Documentation
Django Model Field Default Based Off Another Field
I needed field end_day to default to the last day of month in serializer. So I did this:
class MySerializer(serializers.Serializer):
year = serializers.IntegerField()
month = serializers.IntegerField()
end_day = serializers.IntegerField(required=False, default=None)
def end_day_value(self):
return self.validated_data['end_day'] or \
(datetime(year=self.validated_data['year'], month=self.validated_data['month'])+relativedelta(months=1)-relativedelta(days=1)).day
So when I need end_day or its default value I just call end_day_value method.

Django admin and autocomplete-light - how to override save method

I'm trying to include Django-autocomplete-light in my project. Everything works as expected including the creation of new choice by autocomplete. The only problem is my model contains more than one field and I'm using autocomplete only on the 'name' field. When I save my new record django-admin creates new object with the same name in the database instead of updating the record already created by autocomplete. At the end I have two records, one half empty created by autocomplete, and one valid created by django-admin.
models.py
class Montinent(models.Model):
name = models.CharField(max_length=250)
code = models.CharField(max_length=2, unique=True, db_index=True)
created = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ('-name',)
def __str__(self):
return self.name
views.py
class MontinentAutocomplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
#Don't forget to filter out results depending on the visitor !
if not self.request.user.is_authenticated():
return Montinent.objects.none()
qs = Montinent.objects.all()
if self.q:
qs = qs.filter(name__istartswith=self.q)
return qs
urls.py
urlpatterns = [
url(r'^admin/', include(admin.site.urls)),
url(r'^montinent-autocomplete/$', MontinentAutocomplete.as_view(create_field='name'), name='montinent-autocomplete',),
]
admin.py
class MontinentForm(forms.ModelForm):
name = forms.ModelChoiceField(
queryset = Montinent.objects.all(),
widget = autocomplete.ModelSelect2(url='montinent-autocomplete')
)
class Meta:
model = Montinent
fields = ['name', 'slug', 'code']
class MontinentAdmin(admin.ModelAdmin):
form = MontinentForm
admin.site.register(Montinent, MontinentAdmin)
The way autocomplete creates new choice is as follow:
When the user selects that option, the autocomplete script will make a
POST request to the view. It should create the object and return the
pk, so the item will then be added just as if it already had a PK.
In this case it looks like I need to override the default django-admin save method. I tried just to make the 'name' field unique but in this case Django says this name already exist.
My question is how to override the default save method in my models.py so django to use the returned from autocomplete pk and append the missing information to the row instead of creating new object?
I was also struggling to fill more than one field with the autocomplete create choice. I needed to include the user who is creating the new entry.
The method that saves the new entry into the database is the create_object(text) method from autocomplete.Select2QuerySetView. You can read more about this method in the documentation http://django-autocomplete-light.readthedocs.io/en/master/api.html
So, to include a user I just override the method as follow:
def create_object(self, text):
return self.get_queryset().create(**{self.create_field: text, 'user' : self.request.user})
Now you don't need to have partially filled forms using the autocomplete create options. Just fill it with any field you want to.

Filter M2M in template?

In my model, I have the following M2M field
class FamilyMember(AbstractUser):
...
email_list = models.ManyToManyField('EmailList', verbose_name="Email Lists", blank=True, null=True)
...
The EmailList table looks like this:
class EmailList(models.Model):
name = models.CharField(max_length=50, default='My List')
description = models.TextField(blank=True)
is_active = models.BooleanField(verbose_name="Active")
is_managed_by_user = models.BooleanField(verbose_name="User Managed")
In the app, the user should only see records that is_active=True and is_managed_by_user=True.
In the Admin side, the admin should be able to add a user to any/all of these groups, regardless of the is_active and is_managed_by_user flag.
What happens is that the Admin assigns a user to all of the email list records. Then, the user logs in and can only see a subset of the list (is_active=True and is_managed_by_user=True). This is expected behavior. However, what comes next is not.
The user deselects an email list item and then saves the record. Since M2M_Save first clears all of the m2m records before it calls save() I lose all of the records that the Admin assigned to this user.
How can I keep those? I've tried creating multiple lists and then merging them before the save, I've tried passing the entire list to the template and then hiding the ones where is_managed_by_user=False, and I just can't get anything to work.
What makes this even more tricky for me is that this is all wrapped up in a formset.
How would you go about coding this? What is the right way to do it? Do I filter out the records that the user shouldn't see in my view? If so, how do I merge those missing records before I save any changes that the user makes?
You might want to try setting up a model manager in your models.py to take care of the filtering. You can then call the filter in your views.py like so:
models.py:
class EmailListQuerySet(models.query.QuerySet):
def active(self):
return self.filter(is_active=True)
def managed_by_user(self):
return self.filter(is_managed_by_user=True)
class EmailListManager(models.Manager):
def get_queryset(self):
return EmailListQuerySet(self.model, using=self._db)
def get_active(self):
return self.get_queryset().active()
def get_all(self):
return self.get_queryset().active().managed_by_user()
class EmailList(models.Model):
name = models.CharField(max_length=50, default='My List')
description = models.TextField(blank=True)
is_active = models.BooleanField(verbose_name="Active")
is_managed_by_user = models.BooleanField(verbose_name="User Managed")
objects = EmailListManager()
views.py:
def view(request):
email = EmailList.objects.get_all()
return render(request, 'template.html', {'email': email})
Obviously there is outstanding data incorporated in my example, and you are more than welcome to change the variables/filters according to your needs. However, I hope the above can give you an idea of the possibilities you can try.
In your views you could do email = EmailList.objects.all().is_active().is_managed_by_user(), but the loading time will be longer if you have a lot of objects in your database. The model manager is preferred to save memory. Additionally, it is not reliant on what the user does, so both the admin and user interface have to talk to the model directly (keeping them in sync).
Note: The example above is typed directly into this answer and has not been validated in a text editor. I apologize if there are some syntax or typo errors.

Update only specific fields in a models.Model

I have a model
class Survey(models.Model):
created_by = models.ForeignKey(User)
question = models.CharField(max_length=150)
active = models.NullBooleanField()
def __unicode__(self):
return self.question
and now I want to update only the active field. So I do this:
survey = get_object_or_404(Survey, created_by=request.user, pk=question_id)
survey.active = True
survey.save(["active"])
Now I get an error IntegrityError: PRIMARY KEY must be unique.
Am I right with this method to update?
To update a subset of fields, you can use update_fields:
survey.save(update_fields=["active"])
The update_fields argument was added in Django 1.5. In earlier versions, you could use the update() method instead:
Survey.objects.filter(pk=survey.pk).update(active=True)
Usually, the correct way of updating certain fields in one or more model instances is to use the update() method on the respective queryset. Then you do something like this:
affected_surveys = Survey.objects.filter(
# restrict your queryset by whatever fits you
# ...
).update(active=True)
This way, you don't need to call save() on your model anymore because it gets saved automatically. Also, the update() method returns the number of survey instances that were affected by your update.

Django: MultipleChoiceField in admin to carry over previously saved values

I am having troubles to carry over previously selected items in a ModelForm in the admin.
I want to use the forms.CheckboxSelectMultiple widget since that is the most straightforward UI in this usecase. It works as far that when saving, the values are stored. But when editing the previously saved item, the values previously saved in this field are not reflected in the widget.
UI Example:
After posting (editing that item, returns it blank):
However, when not using the widget but a regular CharField when editing the item it looks like:
So for some reason the values are not represented by the checkbox widget?
Here's my simplified setup, models.py
POST_TYPES = (
('blog', 'Blog'),
('portfolio', 'Portfolio'),
('beeldbank', 'Beeldbank'),
)
class Module(models.Model):
title = models.CharField(max_length=100, verbose_name='title')
entriesFrom = models.CharField(max_length=100)
def __unicode__(self):
return self.title
forms.py:
class ModuleForm(forms.ModelForm):
entriesFrom = forms.MultipleChoiceField(
choices=POST_TYPES,
widget=CheckboxSelectMultiple,
label="Pull content from",
required=False,
show_hidden_initial=True)
class Meta:
model = Module
def __init__(self, *args, **kwargs):
super(ModuleForm, self).__init__(*args, **kwargs)
if kwargs.has_key('instance'):
instance = kwargs['instance']
self.fields['entriesFrom'].initial = instance.entriesFrom
logging.debug(instance.entriesFrom)
admin.py
class ModuleAdmin(admin.ModelAdmin):
form = ModuleForm
So when editing a previously saved item with say 'blog' selected, debugging on init returns me the correct values on self.fields['entriesFrom'] ([u'blog',]), but its not reflected in the checkboxes (nothing is shown as selected) in the admin.
edit
updated the ModuleForm class to pass on initial values, but nothing still gets pre-populated whilst there are a few values in the initial value ("[u'blog']").
Solution:
Setting the choices by a integer, instead of a string.
POST_TYPES = (
(1, 'Blog'),
(2, 'Portfolio'),
(3, 'Beeldbank'),
)
Damn, that wasn't worth breaking my skull over.
Might not be correct, but for my usecase, I did not want to replace the values with integers (as per the accepted answer). This was arrived at by a smidge of trial-and-error, and a lot of stepping through Django internals. Works for me, but YMMV:
from django.forms.widgets import (
CheckboxSelectMultiple as OriginalCheckboxSelectMultiple,
)
class CheckboxSelectMultiple(OriginalCheckboxSelectMultiple):
def optgroups(self, name, value, attrs=None):
# values come back as e.g. `['foo,bar']`, which we don't want when inferring "selected"
return super().optgroups(name, value[0].split(","), attrs)
Maybe I'm not understanding your question completely, but it seems like you could simplify a little. Using ModelForms, I don't think any of your overriding the _init_ in your form is necessary. Try this and see if you get your desired behavior.
models.py
class Module(models.Model):
POST_TYPES = (
('blog', 'Blog'),
('portfolio', 'Portfolio'),
)
title = models.CharField(max_length=100, verbose_name='title')
entriesFrom = models.CharField(max_length=100, verbose_name='Pull content from', choices=POST_TYPES, blank=True)
def __unicode__(self):
return self.title
forms.py
class ModuleForm(forms.ModelForm):
class Meta:
model = Module
admin.py
from django.contrib import admin
admin.site.register(models.Module)
If my answer isn't what you're looking for, try clarifying your question and I'll see if I can help you out.
you can use this function to remove string mark
from ast import literal_eval
literal_eval(value)
I faced this issue, my changes haven't affected on save.
I use CharField in model, but in forms.py;
class ModuleForm(forms.ModelForm):
my_field = forms.MultipleChoiceField(
choices=POST_TYPES,
widget=CheckboxSelectMultiple,
required=False,)
def __init__(self, *args, **kwargs):
super(ModuleForm, self).__init__(*args, **kwargs)
if kwargs.get('instance'):
self.initial['my_field'] = eval(self.initial['my_field'])
This form solution worked for me on MultipleChoiceField on Django Admin.