text manipulation in django as soon as user/admin enters it - django

i have a simple model:
class Article(models.Model):
name = models.CharField(max_length=1000)
custom_name = models.CharField(max_length=1000)
custom function:
def process_text(my_string):
return len(my_string)
i want the following:
custom_name = process_text(name)
Suppose the admin enters name as Mark Pilgrim then custom_name should have the auto populated value of 12.
in the admin.py can i have something like
prepopulated_fields
what would be an easy way to go about it.
Thanks!!

The easiest way to do this is to add a method that listens on the pre_save signal.
Here is a sample you can use (this code goes in your models.py for the app)
from django.db.models.signals import pre_save
from django.dispatch import receiver
# Your models go here
def process_text(mystring):
return len(mystring)
#receiver(pre_save, sender=Article)
def my_handler(sender, **kwargs):
if not kwargs['raw']:
obj = kwargs['instance']
obj.custom_name = process_string(obj.name)
The signals documentation has more information on signals, and the pre_save documentation lists what arguments the method expects.

If you have form with those fields then you can use some javascript to update the second field when the first lost focus. Then when someone will enter something in to the first field and leave the field, js can calculate length and put the value in second field.
If this should be done in the backend level - then you can for example override model's save() method. More here - https://docs.djangoproject.com/en/dev/topics/db/models/#overriding-predefined-model-methods
Cheers.

In my opinion the best option is to set the value on the form validation stage.

Related

Django - cascade on other condition

I'm using Django and a Postgres database and I have two tables (Authors, Books).
I don't want to enable to deletion of any records, instead to change the boolean of an unseen field called 'active' - a field both tables have. What i'd like to happen is when a user changes an Author's 'active' field to False, all of the books with a FK to this author in the Books table have their 'active' field's also set to False.
In Django, my only options it seems related to deletion, how would I set this up with the models.py file?
You can solve this with signals. In the following example I assume your Book model has a foreign key field that looks like this.
class Book(models.Model):
...
author = models.ForeignKey("Author", related_name="books")
...
Option 1. Use Signals
An example function hooked into the Author post_save signal.
from django.db import transaction
from django.db.models.signals import post_save
def update_books_status(sender, instance, created, **kwargs):
with transaction.atomic():
books = Books.objects.select_for_update().filter(author=instance)
for book in books:
book.active = False
book.save()
post_save.connect(update_books_status, sender=Author)
In this example the atomic transaction ensures that all the updates are run in a single DB query. This saves you from the n+1 issue you would have if you just ran through the query set in a for loop.
Option 2. Override save()
Another way to handle this that is probably better is to override the Author model save() method.
class Author(models.Model):
...
def save(self, *args, **kwargs):
# Make sure we are not saving a new Author
# And we are saving the Author as inactive
# And we are are saving an Author that was active.
if not self._state.adding and not self.active and self._loaded_values['active']:
self.inactivate_books()
super().save(*args, **kwargs)
def inactivate_books(self):
with transaction.atomic():
books = Books.objects.select_for_update().filter(author=self)
for book in books:
book.active = False
book.save()

How to use username as a string in model in django?

I want to use the username of the account in which my django is running as a string to load the model fields specific to that username. I have created a file 'survey.py' which returns a dictionary and I want the keys as the fields.
How can I get the username as string?
from django.db import models
from django.contrib.auth.models import User
from multiselectfield import MultiSelectField
from survey_a0_duplicate import details, analysis
import ast
class HomeForm1(models.Model):
user= models.OneToOneField(User, on_delete=models.CASCADE,)
details.loadData(survey_name = user)#<=====This loads the data for specific user<======
global f1
f1=analysis.getQuestion(in_json=False)#<====We get the dictionary here<========
d=list(f1.keys())
###################assign the filters#######################################################
for k in d:
q=list(f1[k].keys())
q.sort()
choices=tuple(map(lambda f: (f,f),q))
locals()[k]=MultiSelectField(max_length=1000,choices=choices,blank=True)
def save(self, *args, **kwargs):
if self.pk is None:
self.user= self.user.username
super(HomeForm1,self).save(*args,**kwargs)
def __str__(self):
return self.title
This is not how you write Django code. Global variables are a bad idea anyway, but you must not use them in a multi-user, multi-process environment like Django. You will immediately have thread-safety issues; you must not do it.
Not only is there an explicit global in the code you have shown, there is clearly one inside survey_a0_duplicate - since details.loadData() does not actually return anything but you then "get the dictionary" from analysis.getQuestion. You must remove the globals from both locations.
Also, your save method is totally wrong. You have the user relationship; why would you overwrite it with the username? That not only makes no sense, it specifically destroys the type of the field that you have set. Just don't do it. Remove the entire save method.
But you need to stop messing about with choices at class level. That is never going to work. If you need to dynamically set choices, do in in a form, where you can customise the __init__ method to accept the current user and build up the choices based on that.

Django models: Set default relative to another field

I am building an app using Django 1.10 as backend.
Is it possible to set a model field's default relative to another model from the same instance?
I specifically need to set second_visit's default to be 3 weeks after the first_visit
class SomeModel(models.Model):
first_visit = models.DateField()
second_visit = models.DateField(default= second_visit_default)
def second_visit_default(self):
# Set second_visit to 3 weeks after first_visit
You cannot assign a default value on a field dependent on another before having a model instance. To achieve the same you can override the save() method of the model:
class SomeModel(models.Model):
...
def save(self, *args, **kwargs):
self.second_visit = self.first_visit + datetime.timedelta(weeks=3)
super().save(*args, **kwargs)
You can override save or usepre_save
from django.db.models.signals import pre_save
from django.dispatch import receiver
#receiver(pre_save, sender=SomeModel)
def my_handler(sender, instance, **kwargs):
instance.second_visit = # Set second_visit to 3 weeks after instance.first_visit
This is a late answer, but #BjornW addresses a valid concern in the comment on the accepted answer that is not addressed in the 2 provided answers (at the time of writing): Overwriting save is not the same thing as setting a default value; a default value only takes effect once, at the first instance creation, while modifying save affects every subsequent modification of an existing instance as well. The 2nd answer suffers from the same deviation of the principle of a default value.
Providing a default value relative to other attributes of self is not (yet?) possible, however you can use the pre_save signal to set a default value for a field that is relative to other fields.
Here would be an example:
# signals.py
from django.dispatch import receiver
from django.db.models.signals import pre_save
# do all necessary imports e.g.:
from .models import SomeModel
#receiver(pre_save, sender=SomeModel)
def set_default_second_visit(sender, instance, raw, **kwargs):
"""
Set default value for `second_visit`
"""
if instance.pk is None:
instance.second_visit = instance.second_visit or instance.first_visit + datetime.timedelta(weeks=3)
A note might be at hand:
It is not that the pre_save signal is sent only once. Actually, it is sent whenever an instance of SomeModel is saved, i.e. also if an existing entry from the db is modified. The line if instance.pk is None: is present and necessary exactly for this reason: it makes sure that that the value is set only if the object does not yet exist in the database (i.e has no primary key yet).

Adding to the "constructor" of a django model

I want to do an extra initalization whenever instances of a specific django model are created. I know that overriding __init__ can lead to trouble. What other alternatives should I consider?
Update. Additional details: The intent is to initialize a state-machine that the instances of that model represent. This state-machine is provided by an imported library, and it's inner state is persisted by my django-model. The idea is that whenever the model is loaded, the state machine would be automatically initialized with the model's data.
Overriding __init__ might work, but it's bad idea and it's not the Django way.
The proper way of doing it in Django is using signals.
The ones that are of interest to you in this case are pre_init and post_init.
django.db.models.signals.pre_init
Whenever you instantiate a Django
model, this signal is sent at the beginning of the model’s __init__()
method.
django.db.models.signals.post_init
Like pre_init, but this one is sent
when the __init__(): method finishes
So your code should be something like
from django.db import models
from django.db.models.signals import post_init
class MyModel(models.Model):
# normal model definition...
def extraInitForMyModel(**kwargs):
instance = kwargs.get('instance')
do_whatever_you_need_with(instance)
post_init.connect(extraInitForMyModel, MyModel)
You can as well connect signals to Django's predefined models.
While I agree that there often is a better approach than overriding the __init__ for what you want to do, it is possible and there might be cases where it could be useful.
Here is an example on how to correctly override the __init__ method of a model without interfering with Django's internal logic:
from django.db import models
class Book(models.Model):
title = models.CharField(max_length=100)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# add your own logic
The two suggested methods in the docs rely on the instance being created in an arbitrary way:
Add a classmethod on the model class:
from django.db import models
class Book(models.Model):
title = models.CharField(max_length=100)
#classmethod
def create(cls, title):
book = cls(title=title)
# do something with the book
return book
book = Book.create("Pride and Prejudice")
Add a method on a custom manager:
class BookManager(models.Manager):
def create_book(self, title):
book = self.create(title=title)
# do something with the book
return book
class Book(models.Model):
title = models.CharField(max_length=100)
objects = BookManager()
book = Book.objects.create_book("Pride and Prejudice")
If that is your case, I would go that way. If not, I would stick to #vartec's answer.

Django model.save() not working with loaddata

I have a model which is overriding save() to slugify a field:
class MyModel(models.Model):
name = models.CharField(max_length=200)
slug = models.SlugField(max_length=200)
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
super(MyModel, self).save(*args, **kwargs)
When I run load data to load a fixture, this save() does not appear to be called because the slug field is empty in the database. Am I missing something?
I can get it to work by a pre_save hook signal, but this is a bit of a hack and it would be nice to get save() working.
def mymodel_pre_save(sender, **kwargs):
instance = kwargs['instance']
instance.slug = slugify(instance.name)
pre_save.connect(mymodel_pre_save, sender=MyModel)
Thanks in advance.
No you're not. save() is NOT called by loaddata, by design (its way more resource intensive, I suppose). Sorry.
EDIT: According to the docs, pre-save is not called either (even though apparently it is?).
Data is saved to the database as-is, according to https://docs.djangoproject.com/en/dev/ref/django-admin/#what-s-a-fixture
I'm doing something similar now - I need a second model to have a parallel entry for each of the first model in the fixture. The second model can be enabled/disabled, and has to retain that value across loaddata calls. Unfortunately, having a field with a default value (and leaving that field out of the fixture) doesn't seem to work - it gets reset to the default value when the fixture is loaded (The two models could have been combined otherwise).
So I'm on Django 1.4, and this is what I've found so far:
You're correct that save() is not called. There's a special DeserializedObject that does the insertion, by calling save_base() on the Model class - overriding save_base() on your model won't do anything since it's bypassed anyway.
#Dave is also correct: the current docs still say the pre-save signal is not called, but it is. It's behind a condition: if origin and not meta.auto_created
origin is the class for the model being saved, so I don't see why it would ever be falsy.
meta.auto_created has been False so far with everything I've tried, so I'm not yet sure what it's for. Looking at the Options object, it seems to have something to do with abstract models.
So yes, the pre_save signal is indeed being sent.
Further down, there's a post_save signal behind the same condition that is also being sent.
Using the post_save signal works. My models are more complex, including a ManyToMany on the "Enabled" model, but basically I'm using it like this:
from django.db.models.signals import post_save
class Info(models.Model):
name = models.TextField()
class Enabled(models.Model):
info = models.ForeignKey(Info)
def create_enabled(sender, instance, *args, **kwards):
if Info == sender:
Enabled.objects.get_or_create(id=instance.id, info=instance)
post_save.connect(create_enabled)
And of course, initial_data.json only defines instances of Info.