In Django Admin I am displaying a url.
This url is created using the id the object that it is attached to.
I'm using python, django and django-rest-framework.
In my views I have logic on the ApiDetail class. Here I override the 'get' method.
I increment the current object in views.py:
currentObject = Api.objects.get(id=pk)
currentObject.currentNumber += 1
currentObject.save()
return self.retrieve(request, *args, **kwargs)
In models.py I set the url field:
class Api(models.Model):
myUrl = models.CharField(max_length=500, blank=True, verbose_name="Url", editable=False)
def save(self, *args, **kwargs):
self.formUrl = "https://custumUrl/"+str(self.id)+"/"
super(Api, self).save(*args, **kwargs)
Here I override the api save method to update the formUrl field.
The problem I have is when a form is first added to Django admin and saved the url says:
https://custumUrl/none/
It should say:
https://custumUrl/1/
Or any number, but definitely the number of the objects id.
I think Daniel is right in their comments and you should follow their advice.
But if you don't want to do that, then you should first save an object, then assign an id value to the url, then save it again:
class Api(models.Model):
myUrl = models.CharField(max_length=500, blank=True, verbose_name="Url", editable=False)
def save(self, *args, **kwargs):
super(Api, self).save(*args, **kwargs)
self.formUrl = "https://custumUrl/"+str(self.id)+"/"
super(Api, self).save(*args, **kwargs)
Is currentNumber defined in he Api class?
Also, in your Api class, you have myUrl defined, but in the save method it's formUrl.
Maybe try something like this:
class Api(models.Model):
formUrl = models.CharField(max_length=500, blank=True, verbose_name="Url", editable=False)
def save(self):
"""If this is the firsts time populate required details, otherwise update it."""
if not self.id:
latest_obj = Api.latest('id')
this_id = latest_obj.id
self.formUrl = "https://custumUrl/"+str(this_id)+"/"
super(Api, self).save()
else:
#Save it as is
super(Api, self).save()
Related
I have this code which for my views
class QuestionViewSet(viewsets.ModelViewSet):
queryset=Question.objects.all()
lookup_field="slug"
serializer_class=QuestionSerializer
permission_classes=[IsAuthorOrReadOnly,IsAuthenticated]
def perform_create(self, serializer):
print("user is", self.request.user)
serializer.save(author=self.request.user)
and this code for serializers
class QuestionSerializer(serializers.ModelSerializer):
author = serializers.StringRelatedField(read_only=True)
created_at = serializers.SerializerMethodField()
slug = serializers.SlugField(read_only=True)
answers_count = serializers.SerializerMethodField()
user_has_answered = serializers.SerializerMethodField()
print("author in serializer", author)
class Meta:
model = Question
exclude = ["updated_at"]
def get_created_at(self, instance):
return instance.created_at.strftime("%B %d, %Y")
def get_answers_count(self, instance):
return instance.answers.count()
def get_user_has_answered(self, instance):
request = self.context.get("request")
return instance.answers.filter(author=request.user).exists()
Then i have this code for models
class Question(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
content = models.CharField(max_length=240)
slug = models.SlugField(max_length=255, unique=True)
author = models.ForeignKey(settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="questions")
def save(self, *args, **kwargs):
if not self.id:
# Newly created object, so set slug
self.slug = slugify(self.content)
super(Question, self).save(*args, **kwargs)
What i basically fail to do is to log the request .In case of function based i could easily log the request and see as what data is coming .However in case of class based views i fail to do this .
Also , I don't understand as where data is being saved in case of post request .I don't see ny save etc .
Please enlighten as how this works fine ?
To log a request data you can override a dispatch method:
class QuestionViewSet(viewsets.ModelViewSet):
def dispatch(self, request, *args, **kwargs)
# log the request here
return super().dispatch(request, *args, **kwargs)
Also you may consider using something more "broad" like setting your own midlleware.
As for create - magic happens in viewset create method. You can override it as well:
def create(self, request, *args, **kwargs):
# do something unusual here
instead of overriding the dispatch, better solution you can write the middleware which log the request data.
https://docs.djangoproject.com/en/3.0/topics/http/middleware/
lass SimpleMiddleware:
def __init__(self, get_response):
self.get_response = get_response
# One-time configuration and initialization.
def __call__(self, request):
# Code to be executed for each request before
# the view (and later middleware) are called.
response = self.get_response(request)
# Code to be executed for each request/response after
# the view is called.
return response
What i want: when user download photo from the admin's panel, I want create a preview(thumbnail) and save it into other field of this model, using sorl.thumbnail.
What i do:
from sorl.thumbnail import ImageField, get_thumbnail
class sitePhotos(models.Model):
photo = ImageField(verbose_name=u'Фотография для галереи',
upload_to=upload_galery_photos, null=True)
preview = ImageField(upload_to=upload_galery_previews, editable=False, null=True)
Migrations are doing.
I was trying overwrite save method from class sitePhotos:
def save(self, *args, **kwargs):
self.preview = get_thumbnail(self.photo, '250x250', crop='center', quality=99)
super(sitePhotos, self).save(*args, **kwargs)
Problems: Documentation sorl.thumbnail: https://sorl-thumbnail.readthedocs.io/en/latest/examples.html#low-level-api-examples
im = get_thumbnail(my_file, '100x100', crop='center', quality=99)
What's type of my_file? It is a url, ImageField or what? When start has error 'ImageField' object has no attribute '_committed'
Google: override save method - 'ImageFile' object has no attribute '_committed'
After that i have magic with urls and other. My finish function:
self.preview = get_thumbnail('../'+self.photo.url, '250x250',
crop='center', quality=99).url
May be somebody has working example of this or where can I read about this? Thank you for your answers!
You can use the save method on Image file:
def save(self, *args, **kwargs):
preview = get_thumbnail(self.photo, '250x250', crop='center', quality=99)
self.preview.save(preview.name, ContentFile(preview.read()), save=False)
super(sitePhotos, self).save(*args, **kwargs)
I'm using django-favorites from https://bitbucket.org/last_partizan/django-favorites/overview, and I set everything right and when I run the code it gave me 'FavoriteManager' object has no attribute 'get_query_set'. The error was occuring from the django-favorites models.py from this line
qs = self.get_query_set().filter(content_type=content_type, object_id=obj.pk)
So I thought, I need to specify object. So I'm trying to use fav button for all my Post, so I need to change obj to Post. But even after changing I get same error. What is going on with this?
This is models.py inside favorite app, where error is occurring
def favorites_for_object(self, obj, user=None):
""" Returns Favorites for a specific object """
content_type = ContentType.objects.get_for_model(type(obj))
qs = self.get_query_set().filter(content_type=content_type,
object_id=obj.pk)
if user:
qs = qs.filter(user=user)
return qs
This is where I'm calling fav_item
<div class="actions">{% fav_item post user %}</div>
Lets say I want to put fav_item on my category model
class Category(models.Model):
name = models.CharField(max_length=128, unique=True)
slug = models.CharField(max_length=100, unique=True)
author = models.OneToOneField(settings.AUTH_USER_MODEL, unique=True)
def save(self, *args, **kwargs):
self.slug = uuslug(self.name,instance=self, max_length=100)
super(Category, self).save(*args, **kwargs)
def __unicode__(self):
return self.name
Then doesn't it make sense to import
main.models import Category
and switch obj to Category. Unfortunately there's not much documentation here: https://bitbucket.org/last_partizan/django-favorites/overview
This problem is related to Django version. The method get_query_set has been removed from Django 1.8.x . Before that, this method was used by RenameManagerMethods. Check this github source: https://github.com/django/django/blob/stable/1.7.x/django/db/models/manager.py#L56
Also django-favorites was last updated in 2013, as I can see from the source. You should consider downgrading your Django version.
I have a user profile model with optional avatar that looks like
#models.py
class UserProfile(models.Model):
avatar = models.ImageField(upload_to=avatars null=True, blank=True)
.
.
.
Then a form like:
#forms.py
class UserProfileForm(forms.ModelForm):
avatar = forms.ImageField(required=False,....)
class Meta:
model = UserProfile
Finally, a view including
#views.py
def edit_profile(....)
profile_obj = request.user.userprofile
form = UserProfile(data=request.POST, files=request.FILES, instance=profile_obj)
if form.is_valid():
form.save()
Now when the template for editing the user's profile, and avatar, is rendered, it included a Clear checkbox, which when selected let's the user remove their avatar photo. It leaves myuser.avatar in the state <ImageFieldFile: None>, however it does not delete the file in the storage area of the site itself (i.e. the jpg,png or whatever). I've read that this is by design in Django 1.6, which is all well and good, but how do I override this feature, so that the file is indeed deleted to?
From the shell there exists no problem:
from myapp.models import UserProfile
user1==UserProfile.objects.all()[0]
user1.avatar.delete()
Removes the jpeg too.
EDIT:
I tried using a signal like:
#models.py
.
.
.
#receiver(post_delete, sender=UserProfile)
def avatar_post_delete_handler(sender, **kwargs):
print 'DEBUG: avatar delete triggered'
avatar = kwargs['instance']
storage, path = avatar.original_image.storage, avatar.original_image.path
storage.delete(path)
but this did not even trigger, I guess because I'm not deleting the UserProfile object
in its entirety when the user selects the clear checkbox, but rather just the avatar.
Extend the ImageField like this and use it instead:
class ImageField(models.ImageField):
def save_form_data(self, instance, data):
if data is not None:
file = getattr(instance, self.attname)
if file != data:
file.delete(save=False)
super(ImageField, self).save_form_data(instance, data)
This will delete the old file if you replace it with the new one, or mark it to clear.
Here is the explanation why.
Edit:
There is also an app django-smartfields that includes this functionality plus more, like automatic re-sizing, automatic conversion of images and videos, etc. It achieves it in a more complicated way though, using field descriptors and model customization. But it is very simple to use:
from smartfields import fields
class UserProfile(models.Model):
avatar = fields.ImageField(upload_to='avatars', blank=True)
It will also remove files whenever:
field value was replaced with a new one (either uploaded or set manually)
the model instance itself containing the field is deleted.
Not the best solution at all, but one hackish way might be to store the file for each user under just their username, then on catching a pre_save signal with an empty instance.avatar.name and where the user file exists on disk in the expected place, delete it. Yuck.
class UserProfile(models.Model):
avatar = models.ImageField(upload_to=avatars null=True, blank=True)
and
#save the avatar for each user as their username
def update_filename(instance, filename):
path = "avatars"
format = instance.user.username
return os.path.join(path, format)
and
#if current instance has empty avatar.name
#and the file exists on disk where expected for user
#deduce user has clicked clear, delete file for user.
#receiver(pre_save, sender=UserProfile)
def avatar_pre_save_handler(sender, instance, **kwargs):
avatar_filepath = settings.MEDIA_ROOT +'/avatars/'+ instance.user.username
if not instance.avatar.name and os.path.isfile(avatar_filepath):
os.remove(avatar_filepath)
One more possible way (that doesn't require special naming of files) is overriding form save method, then calling it with the old avatar as kwarg:
#forms.py
class UserProfileForm(forms.ModelForm):
def save(self, commit=True, *args, **kwargs):
instance = super(UserProfileForm, self).save(commit=False)
old_avatar_name = kwargs.pop('old_avatar_name', None)
new_avatar_name = None
if self.cleaned_data['avatar']:
new_avatar_name = self.cleaned_data['avatar'].name
if old_avatar_name != new_avatar_name:
old_avatar_filepath = settings.MEDIA_ROOT +'/'+ old_avatar_name
if os.path.isfile(old_avatar_filepath):
os.remove(old_avatar_filepath)
if commit:
instance.save()
return instance
Then in the view:
def edit_profile(request,....):
.
.
.
try:
profile_obj = request.user.userprofile
except ObjectDoesNotExist:
return HttpResponseRedirect(reverse('profiles_create_profile'))
if profile_obj.avatar.name:
avatar_kwargs={'old_avatar_name': profile_obj.avatar.name}
else:
avatar_kwargs={}
.
.
.
if form.is_valid():
form.save(**avatar_kwargs)
Use Mixin like below
class ImageDeleteMixin(object):
def delete(self, *args, **kwargs):
if self.avatar:
storage, path = self.avatar.storage, self.avatar.path
super(ImageDeleteMixin, self).delete(*args, **kwargs)
storage.delete(path)
else:
super(ImageDeleteMixin, self).delete(*args, **kwargs)
def save(self, *args, **kwargs):
if self.id:
old_instance = self.__class__._default_manager.get(pk=self.pk)
if (
old_instance.avatar != self.avatar and old_instance.avatar and
old_instance.avatar.path
):
storage, path = old_instance.avatar.storage, old_instance.avatar.path
super(ImageDeleteMixin, self).save(*args, **kwargs)
storage.delete(path)
return
return super(ImageDeleteMixin, self).save(*args, **kwargs)
class UserProfile(ImageDeleteMixin, models.Model):
avatar = models.ImageField(upload_to=avatars null=True, blank=True)
Depending on your use case, you could hook into Django's Singals:
https://docs.djangoproject.com/en/dev/ref/signals/#post-delete
Or, in a view, when the "checked" variable is sent back to the server, delete the avatar!
Banging head against the wall again.
I'm trying to add tags using other known fields
# models.py
class MyModel(models.Model):
...
tags = models.ManyToManyField(Tag, blank=True)
field_m2m = models.ManyToManyField('M2mModel', blank=True)
field_fk = models.ForeignKey('FkModel', blank=True, null=True)
...
def save(self, *args, **kwargs):
for inst in self.field_m2m.all():
self.tags.add(Tag.objects.get(name=inst.name))
self.tags.add(Tag.objects.get(name=self.field_fk.name))
super(MyModel, self).save(*args, **kwargs)
class FkModel(models.Model):
name = models.CharField(max_length=255, unique=True)
...
class M2mModel(models.Model):
name = models.CharField(max_length=255, unique=True)
...
I am 100% sure my field_m2m and field_fk aren't empty and what's not less important: there are instances corresponding to EXISTING tags. I have other functions covering this part well. I have also tried hardcoding the strings (Tag.objects.get(name="mystring")) to be 101% sure.
Yet, no tags are assigned through admin panel.
I tried to go through the steps in shell and it works there.
>>> m = MyModel.objects.get(name='something')
>>> t = Tag.objects.get(name='sometag')
>>> m.tags.add(t)
>>> m.tags.all()
[<Tag: sometag>]
How to make it work from save() method?
Also until the the model instance is created for the first time, traceback is complaining about: "<MyModel: Something>" needs to have a value for field "mymodel" before this many-to-many relationship can be used.
I guess I should save the model instance before even doing aforementioned assignments, right? How can I do it all at once?
Seems to me that your MyModel instance must be saved into database before saving any relationships. It makes sense because for the relationships, the MyModel's id is needed. So you can change the order of the save method like this:
def save(self, *args, **kwargs):
# notice that super class save go first.
super(MyModel, self).save(*args, **kwargs)
# also this cicle doesn't make sense since self is a newly
# created instance so it won't have anythin in field_m2m.all()
for inst in self.field_m2m.all():
self.tags.add(Tag.objects.get(name=inst.name))
# this should work ok if get returns any tag.
self.tags.add(Tag.objects.get(name=self.field_fk.name))
Hope this helps!
Figured it out thanks to this hint: https://stackoverflow.com/a/6200451/1344854
Model save() method stays default. First I tied a tag to my FkModel and M2mModel instances. The rest of the job is done in ModelAdmin.
# models.py
class FkModel(models.Model):
name = models.CharField(max_length=255, unique=True)
tag = models.ForeignKey(Tag, blank=True, null=True, related_name='fk')
...
class M2mModel(models.Model):
name = models.CharField(max_length=255, unique=True)
tag = models.ForeignKey(Tag, blank=True, null=True, related_name='m2m')
...
# admin.py
class MyModelAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
form.save() # as I learned, without saving at this point, obj.field_m2m.all() will be empty
tags = []
if obj.field_m2m.all():
tags += [m2m.tag for m2m in obj.field_m2m.all()]
if obj.field_fk:
tags += [obj.field_fk.tag]
form.cleaned_data['tags'] = tags
super(MyModelAdmin, self).save_model(request, obj, form, change)