django model save - override method not invoked during migrations - python

I have a save override method in my model class, which generates a new slug each time an object is saved.
def save(self, *args, **kwargs):
if self.column2:
self.slug = slugify(self.column1 + " " + self.column2)
else:
self.slug = slugify(self.column1)
print slug
super(MyModel, self).save(*args, **kwargs)
When I try to create a new object by logging into the python shell, I see the save method is being invoked.
python manage.py shell
>>> MyModel(column1="test",column2="2015").save()
slug is test-2015
However when I am running a migration, this save override method is not being called. Here's part of my migration script..
...
def add_myModel_details(apps, schema_editor):
x = apps.get_model("myapp","myModel")
MyModel(column1 = "test", column2="2015" ).save()
.....
The slug is empty, as the save override isn't being called.

Custom model methods are not available during migrations.
Instead, you can run put code in your RunPython function that modifies your model instances the way the custom save() would have.
References:
This answer

It happens because migrations don't call your save method.
I think save method is not the best place for generate slug. Will be better to use AutoSlugField or signals.
1. signals:
In your case you may use signal pre_save.
Example:
#receiver(pre_save, sender=MyModel)
def my_handler(sender, **kwargs):
my_model = kwargs.get('instance')
if my_model.column2:
my_model.slug = slugify(my_model.column1 + " " + my_model.column2)
else:
my_model.slug = slugify(my_model.column1)
print my_model.slug
2. AutoSlugField:
It's not a standard field but a lot of libraries implement it. I use AutoSlugField from django-extensions. This field uses signals too.
Example:
slug = AutoSlugField(populate_from=("column1", "column2"))
3. save method and migrations
But if you still want to use a save method to generating slug I'd recommend you create data migration and add slugs manually.
Data Migrations django >= 1.7
Data Migrations south

Related

Django - access ManyToManyField right after object was saved

I need to notify users by email, when MyModel object is created. I need to let them know all attributes of this object including ManyToManyFields.
class MyModel(models.Model):
charfield = CharField(...)
manytomany = ManyToManyField('AnotherModel'....)
def to_email(self):
return self.charfield + '\n' + ','.join(self.manytomany.all())
def notify_users(self):
send_mail_to_all_users(message=self.to_email())
The first thing I tried was to override save function:
def save(self, **kwargs):
created = not bool(self.pk)
super(Dopyt, self).save(**kwargs)
if created:
self.notify_users()
Which doesn't work (manytomany appears to be empty QuerySet) probably because transaction haven't been commited yet.
So I tried post_save signal with same result - empty QuerySet.
I can't use m2mchanged signal because:
manytomany can be None
I need to notify users only if object was created, not when it's modified
Do you know how to solve this? Is there some elegant way?

How can I test post-save, pre-save in the full system testing in Django with REST

I have the application in Django REST as backend and Angular as frontend.
Suppose in This is my code
class ModelClass (models.Model):
name = models.CharField(max_length=100)
email = models.EmailField()
def save(self, *args, **kwargs):
#check if the row with this hash already exists.
if not self.pk:
self.hash = self.create_hash()
self.my_stuff = 'something I want to save in that field'
# call to some async task
super(ModelClass, self).save(*args, **kwargs)
In my REST i have this view
class ModelListCreateView(generics.ListCreateAPIView):
model = ModelClass
serializer_class = ModelClassSerializer
def pre_save(self, obj):
obj.created_by = obj.updated_by = self.request.user.staff
def post_save(self, obj, created=False):
# add some other child objects of other model
I don't want to do unit testing. I want to do system testing so that I need to know if I post something to that view then
Pre-save thing should work
Record gets created
Save method of Model gets called with his stuff
After save method in REST gets called
Then I can assert all that stuff.
Can I test all that . I want to know which thing I need to have that sort of test rather than small unit tests
I am confused do I need to use Django test or REST Test or selenium test or PyTEst or factory boy because i want to know if things are actually getting in database
What you are looking is some kind of a REST Client code that would then be able to run your tests and you would be able to verify if the call is successful or not. Django Rest Framework has the APIRestFactory helper class that will aid you in writing such tests. The documentation can be found here and specifically look at the Example section. This would be part of your Django tests

Automate some entries in Django models passing the entry value from same model

This is my models.py code:
from django.db import models
import shortuuid
class website(models.Model):
url = models.URLField(max_length=100)
uid = models.CharField(unique = True, max_length=40,default=str(shortuuid.uuid(name=url)))
def __unicode__(self):
return self.url
In django admin panel the value of uid does not change when I enter a URL.I just want to enter the URL and then want to generate the uid using shortuuid function.I want to set uid as editable=False but before that I want to ensure that the function is woking properly.How to automate the uid value passing url as input?
That's not where you would do it. You can't write something at the class level that depends on an instance attribute of the class: it's simply not possible. And what's more, a default is allocated when the object is instantiated, but you want that to change after the user has changed the value of another attribute, so this isn't a default at all.
Instead you probably want to define this value on save. That's easy to do by simply overriding the save method:
def save(self, *args, **kwargs):
if not self.uid:
self.uid = str(shortuuid.uuid(name=self.user))
return super(website, self).save(*args, **kwargs)

Django admin error in many-to-many relationship

For example.
class One(models.Model):
text=models.CharField(max_length=100)
class Two(models.Model):
test = models.Integer()
many = models.ManyToManyField(One, blank=True)
When I try save my object in admin panel, I take error such as:
"'Two' instance needs to have a primary key value before a many-to-many relationship can be used."
I use django 1.3. I tried add AutoField to Two class, but it's not work too.
This is my code.
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render_to_response, redirect
from django.template import RequestContext
from django.core.urlresolvers import reverse
from project.foo.forms import FooForm
from project.foo.models import Foo
from project.fooTwo.views import fooTwoView
def foo(request, template_name="foo_form.html"):
if request.method == 'POST':
form = FooForm(data=request.POST)
if form.is_valid():
foo = Foo()
foo.name = request.POST.get("name")
foo.count_people = request.POST.get("count_people")
foo.date_time = request.POST.get("date_time")
foo.save()
return fooTwoView(request)
else:
form = FooForm()
return render_to_response(template_name, RequestContext(request, {
"form": form,
}))
P.S. I find my fail. It is in model. I used many-to-many in save method. I add checking before using, but it's not help.
class Foo(models.Model):
name = models.CharField(max_length=100, null=False, blank=False)
count_people = models.PositiveSmallIntegerField()
menu = models.ManyToManyField(Product, blank=True, null=True)
count_people = models.Integer()
full_cost = models.IntegerField(blank=True)
def save(self, *args, **kwargs):
if(hasattr(self,'menu')):
self.full_cost = self.calculate_full_cost()
super(Foo, self).save(*args, **kwargs)
def calculate_full_cost(self):
cost_from_products = sum([product.price for product in self.menu.all()])
percent = cost_from_products * 0.1
return cost_from_products + percent
I try hack in save method such as
if(hasattr(self,Two)):
self.full_cost = self.calculate_full_cost()
This is help me, but i dont think that is the django way. What is interesting, that is without this checking admin panel show error, but create object. Now, if i select item from Two and save, my object does not have full_cost, but when i view my object, admin panel remember my choice and show me my Two item, what i select... I dont know why.
How do i save this?
There are quite a few problems with your code. The most obvious one are
1/ in your view, using a form for user inputs validation/sanitization/conversion then ignoring the santized/converted data and getting unsanitized inputs directly from the request. Use form.cleaned_data instead of request.POST to get your data, or even better use a ModelForm which will take care of creating a fully populated Foo instance for you.
2/ there's NO implicit "this" (or "self" or whatever) pointer in Python methods, you have to explicitely use "self" to get at the instance attributes. Here's what your model's "save" method really do:
def save(self, *args, **kwargs):
# test the truth value of the builtin "id" function
if(id):
# create a local variable "full_cost"
full_cost = self.calculate_full_cost()
# call on super with a wrong base class
super(Banquet, self).save(*args, **kwargs)
# and exit, discarding the value of "full_cost"
Now with regard to your question: Foo.save is obviously not the right place to compute someting based on m2m related objects. Either write a distinct method that run the computation AND update Foo AND save it and call it after the m2m are saved (hint : a ModelForm will take care of saveing the m2m related objects for you), or just use the m2m_changed signal.
This being said, I strongly suggest you spend a few hours learning Python and Django - it will save you a lot of time.
Why not use "OneToOneField" instead of Many-to-Many

Replacing a Django image doesn't delete original

In Django, if you have a ImageFile in a model, deleting will remove the associated file from disk as well as removing the record from the database.
Shouldn't replacing an image also remove the unneeded file from disk? Instead, I see that it keeps the original and adds the replacement.
Now deleting the object won't delete the original file only the replacement.
Are there any good strategies to doing this? I don't want to have a bunch of orphan files if my users replace their images frequently.
The best strategy I've found is to make a custom save method in the model:
class Photo(models.Model):
image = ImageField(...) # works with FileField also
def save(self, *args, **kwargs):
# delete old file when replacing by updating the file
try:
this = Photo.objects.get(id=self.id)
if this.image != self.image:
this.image.delete(save=False)
except: pass # when new photo then we do nothing, normal case
super(Photo, self).save(*args, **kwargs)
And beware, as with the updating which doesn't delete the back end file, deleting an instance model (here Photo) will not delete the back-end file, not in Django 1.3 anyway, you'll have to add more custom code to do that (or regularly do some dirty cron job).
Finally test all your update/delete cases with your ForeignKey, ManytoMany and others relations to check if the back-end files are correctly deleted. Believe only what you test.
Shouldn't replacing an image also remove the unneeded file from disk?
In the olden days, FileField was eager to clean up orphaned files. But that changed in Django 1.2:
In earlier Django versions, when a model instance containing a FileField was deleted, FileField took it upon itself to also delete the file from the backend storage. This opened the door to several potentially serious data-loss scenarios, including rolled-back transactions and fields on different models referencing the same file. In Django 1.2.5, FileField will never delete files from the backend storage.
The code in the following working example will, upon uploading an image in an ImageField, detect if a file with the same name exists, and in that case, delete that file before storing the new one.
It could easily be modified so that it deletes the old file regardless of the filename. But that's not what I wanted in my project.
Add the following class:
from django.core.files.storage import FileSystemStorage
class OverwriteStorage(FileSystemStorage):
def _save(self, name, content):
if self.exists(name):
self.delete(name)
return super(OverwriteStorage, self)._save(name, content)
def get_available_name(self, name):
return name
And use it with ImageField like so:
class MyModel(models.Model):
myfield = models.ImageField(
'description of purpose',
upload_to='folder_name',
storage=OverwriteStorage(), ### using OverwriteStorage here
max_length=500,
null=True,
blank=True,
height_field='height',
width_field='width'
)
height = models.IntegerField(blank=True, null=True)
width = models.IntegerField(blank=True, null=True)
If you don't use transactions or you don't afraid of loosing files on transaction rollback, you can use django-cleanup
There have been a number of tickets regarding this issue though it is likely this will not make it into the core. The most comprehensive is http://code.djangoproject.com/ticket/11663. The patches and ticket comments can give you some direction if you are looking for a solution.
You can also consider using a different StorageBackend such as the Overwrite File Storage System given by Django snippet 976. http://djangosnippets.org/snippets/976/. You can change your default storage to this backend or you can override it on each FileField/ImageField declaration.
Here is a code that can work with or without upload_to=... or blank=True, and when the submitted file has the same name as the old one.
(py3 syntax, tested on Django 1.7)
class Attachment(models.Model):
document = models.FileField(...) # or ImageField
def delete(self, *args, **kwargs):
self.document.delete(save=False)
super().delete(*args, **kwargs)
def save(self, *args, **kwargs):
if self.pk:
old = self.__class__._default_manager.get(pk=self.pk)
if old.document.name and (not self.document._committed or not self.document.name):
old.document.delete(save=False)
super().save(*args, **kwargs)
Remember that this kind of solution is only applicable if you are in a non transactional context (no rollback, because the file is definitively lost)
I used a simple method with popen, so when i save my Info model i delete the former file before linking to the new:
import os
try:
os.popen("rm %s" % str(info.photo.path))
except:
#deal with error
pass
info.photo = nd['photo']
I save the original file and if it has changed - delete it.
class Document(models.Model):
document = FileField()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._document = self.document
def save(self, *args, **kwargs):
if self.document != self._document:
self._document.delete()
super().save(*args, **kwargs)

Categories

Resources