Django save image using upload_to instance keeps instance properties None - django

I Am trying to give an uploaded image a nicer path, using this code (in models.py):
def get_image_path_photos(instance, filename):
return os.path.join('photos', str(instance.someproperty), filename)
and the model
class Photo(models.Model):
someproperty = models.CharField(max_length=17, blank=False, null=False, default="something")
photo = models.ImageField(upload_to=get_image_path_photos, blank=True, null=True)
When I save this after a new insert, it saves it in the path /photos/something/ (it keeps using the default value).
When I edit it, add a photo and save it, it will save it to the correct path.
So it must have something to do that while saving the new object, it doesn't exist yet.
I tried the same with instance.id and this keeps being None as well (I read using auto increment on the id solves this, but this sounds as using the default value as well, and using the default pk/id is auto increment).
I found some simular questions, but none with the answer that solves my problem.
I thought of going to use the pre_save signal.... but somehow my guts says this isn't the right way.
The solution of my problem I found out myselve, please see my answer... A good lesson, don't use slugname definitions the same as the field name.....

Sorry about this. The problem is a bit more complicated. I use the field someproperty also in the url as a slug on the posts....
I just found out something I didn't expected.
i did my post (using django rest framework) from the url using the default value in the url... but I filled in the field with something else.
than, because I define the slugname the same as the fieldname, it overwrites anything you fill in in the field with the value from the url....
This isn't exactly what I meant to be done, but makes sence.
Probably the solution is to call the slug name not the same as the field name......
I keep this question and answer anyway, because for me it was quite a puzzle..... (might be of help to somebody)
as an addition to the answer of jpic:
I used the urls in django rest framwwork, lets say; http:\someurl\api\photos\\
and post there the photo.
posting the photo avatar_big.png using someproperty=bar:
saved the photo in photos\something\ when using the url http\someurl\api\photos\something
and saved the photo in photos\bar\ when using the url http:\someurl\api\photos\bar
the problem is (i guess, still have to check this) that the slug name I use for the url is the same as the fieldname.
this is the code I use in views.py (class based view I use in the django-rest-framework):
class PhotoBySomePropertyListOrCreateModelView(ListOrCreateModelView):
permissions = (IsAuthenticated, )
form = PhotoForm
def get_queryset(self):
someproperty=self.kwargs['someproperty']
return Photo.objects.filter(someproperty=someproperty)
and in urls.py:
url(r'^api/photos/(?P<someproperty>[\w:]+)/$', PhotoBySomePropertyListOrCreateModelView.as_view(resource=PhotoResource)),
here you see the problem, it doesn't listen to the field in 'someproperty', but to the value in the url ....
changing it in
url(r'^api/photos/(?P[\w:]+)/$', PhotoBySomePropertyListOrCreateModelView.as_view(resource=PhotoResource)),
should do the trick.... also adjust the view of course

Related

Django Forms: Don't upload new file on edit. Conditional logic

I HAVE scoured the web and if there is a solution I have not found it. Perhaps the answer is right in front of me but I don't see it and I'm really baffled at this issue.
Problem: I have a model:
class Campaign(models.Model):
emailaddress = models.CharField(max_length=100, default='', verbose_name='ADF Email', blank=True)
xlsfile = models.FileField(upload_to='xlsfiles/', blank=True, null=True)
created = models.DateTimeField(auto_now_add=True)
dealerimage = models.ForeignKey('vipapp.savedImage', blank=True, null=True, related_name='campaign')
I do not override the admin add/change form currently. Perhaps I should, we'll see.
When I submit a new campaign an xlsfile and dealerimage are uploaded and should be required. I use if(self.xlsfile): in the overridden save() on the campaign model to check for the optional xlsfile and if it exists I have a considerable amount of code that parses the xlsfile, puts the records into another model called weblist and also sends the user a modified excel file (I am using both xlsfile and excel file naming on purpose in this post).
The problem is this: When the user edits a campaign using the default admin change form the excel file is listed in the xlsfile field. On submit, the system appears to try again to upload the excel file and that file may no longer actually exist on the system from which the user is editing. On edit, the user may not want to upload a new excel file as this would overwrite the other model (weblist).
Question: How would I clear this field in the change form yet not delete it from the object when the user submits? I cannot use exclude because the user MAY want to upload a new excel file.
Question: What is self.xlsfile in the save() on the campaign model? I mean, how do I tell the difference between what is in the database already (maybe obj.xlsfile) and what is coming in from the change form POST? Is new data in the post self.xlsfile or is that what is in the object already? I know I should be able to determine these things on my own but I've not been able to reliable tell if my test is correct so that it gives me the correct answer.
How do I effectively clear the field when the form loads? The user MAY want to replace the xls and therefore delete the weblist records but this is not typical. Replacing the weblist instances in the weblist model is dangerous because these contain generated codes that have already been sent to customers so this rarely happens, so if there is no new excel file the weblist instances should not be touched. If the user does upload a new excel file the current weblist instances should be deleted and re-written however.
exclude on the edit form is not an option because again, xlsfile may sometimes need to be updated though this is rare. I don't want to delete anything, but I don't want the form to show the xls file or make any changes unless the user explicitly uploads a new xlsfile. I may have repeated myself here but I just want to be clear.
I suspect the answer here will affect the dealerimage field as I will probably understand this better in general. Any explanatory info here would be awesome. Also, providing me links to Working with Forms or The Forms API or Form and field Validation pages will likely be unhelpful as I have read these documents. Pointing to an exact answer that I may have missed in those pages however may provide an Ah-ha moment though :)
I figured this out...
I just overrode get_form so that xlsfile field would always be set to blank on load of my CampaignEditForm:
def get_form(self, request, obj=None, **kwargs):
if obj:
obj.xlsfile = ''
kwargs['form'] = CampaignEditForm
else:
kwargs['form'] = CampaignAddForm
return super(campaignAdmin, self).get_form(request, obj, **kwargs)
Then in the save() of my campaign model I just explicitly set the fields I wanted to save, leaving out xlsfile if it was blank so any value in the DB never got deleted unless it was replaced:
if self.xlsfile:
super(Campaign, self).save(*args,**kwargs)
else:
super(Campaign, self).save(update_fields=["dealername", "campaignname", "dealeraddress", "dealercity",
"dealerstate", "dealerzipcode", "dealerphone", "dealeremail1",
"dealeremail2", "dealeremail3", "dealeremail4", "dealeremail5",
"adf", "adfemail", "created", "dealerimage"])

Django making sure user and user profile have same pk

Right now I'm using Django's built in admin system to manage users, to which I've attached a profile to contain additional data using the following:
class Profile(models.Model):
user = models.OneToOneField(User, editable = False)
# Data fields here...
As it stands the User and Profile pk (and accordingly id number) will be the same if and only if the profile is created right after the user is created. I could guarantee that this would be the case during the registration process, and while that would cover most uses, creating users with the admin interface could cause mismatched ids to occur. Thus this does not seem like a very robust way to solve this problem and I'd like to hardcode the pk's to be the same. I'm not sure how to do this.
I thought the following would work:
profile_id = models.IntegerField(default=user.pk, editable = False,
primary_key = True)
But it gives me the error:
AttributeError: 'OneToOneField' has no attribute 'pk'
What's the best way to guarantee that the profile and user have the same pk? Note: I'd really rather not deal with extending the base user model as using the OneToOneField to link the two seems to be sufficient for all my needs.
Thanks!
[edit]
My reasoning for asking the question:
My immediate problem was that I wanted a dictionary of values of the User's Profile, which I was retrieving usingprofile_values = Profile.objects.filter(pk=user.id).values()[0]. This highlighted the bug, and I "hacked" around it last night using pk=user.profile.id instead. In the light of the morning this does not seem like such a terrible hack. However, it seems like having pk discrepancies could lead to quiet and hard to catch bugs down the line, and thus forcing them to match up would be a Good Idea. But I'm new to Django so I'd entirely accept that it is, in fact, never a problem if you're writing your code correctly. That said, for almost academic reasons, I'd be curious to see how this might be solved.
[/edit]
Like you already agree that it was never a problem because we have a OneToOne mapping between the two models.
So when you need to get the profile obj corresponding to a User:
profile_values = Profile.objects.get(user_id=user)
assuming,
class Profile(models.Model):
user = models.OneToOneField(User)
...
If your column name is not user, then use the corresponding name in get query.
Still if you are curious as to how to achieve same pk for both models, then we can set a signal on every save of User model. See the documentation.
def create_profile(sender, **kwargs):
if kwargs["created"]:
p = Profile(user=kwargs["instance"], ...)
p.save()
django.db.models.signals.post_save.connect(create_profile, sender=User)
create_profile() will be called every time any User object is saved.
In this function, we create Profile object only if a new User instance has been created.
If we start from blank slate, then I think this will always make sure that a Profile exists for every User and is created right after User was created; which in turn will give same pk for both models.
pk is a parameter in a filter() query, but not a field name. You probably want to use user.id.

How to bind an image to an edit form in Django?

I have the following Model:
class Listing(models.Model):
name = models.CharField(max_length=50, verbose_name="Title")
images = models.ManyToManyField('Image')
, with the ManyToManyField linking to this Image class:
class Image(models.Model):
thumb = ImageField(upload_to='images/uploads/')
number = models.PositiveSmallIntegerField()
and a corresponding ModelForm like so:
class ListingEditForm(ModelForm):
image1 = ImageField(required=False, label="Photo 1")
image2 = ImageField(required=False, label="Photo 2")
image3 = ImageField(required=False, label="Photo 3")
class Meta:
model = Listing
exclude = ('images')
The idea is to not limit the number of images that can be associated with a Listing in the backend, but at this time I only need 3 images in the form. Uploading the images works fine, but how would you go about binding the form to a Listing instance so that the images are not 'None' when one views the edit form?
Obviously, this alone won't work, because image1, image2 and image3 are only form fields, and not part of the model:
form = forms.ListingEditForm(instance=listing)
So adding a dictionary as the first parameter seems like the obvious thing to do:
form = forms.ListingEditForm({'image1': ...},instance=listing)
but what should the value of that ... be? And how do I retrieve it from the Listing instance?
I'll answer my own question, even though it's not quite the answer I was looking for. I've looked around, and as far as I know, there is no reliable way in HTML to change the contents of a File input field. So, I could be wrong, but even if you send that information with the request, Django will have no way of showing the information in the field (since it doesn't correspond to a file on the local PC).
So, my solution is simply to send the urls of the images with the request, as one normally would:
return render_to_response('edit.html', {'image1': image1_url, ...})
Then, if this information is present, I use jQuery to place the images next to the file input field in the template, and update it if the user selects a new file. It's not the best, but it works.
I'll still be glad to hear any other solutions.
I would use foreign key relation in Image, and inlineformset_factory for generating the form.
ListingEditForm = inlineformset_factory(Listing, Image, max_num=3, extra=0)
I would also add image name field in Image model. That way user will have indication of uploaded files in form display, and he will also be able to delete images if he whishes so. If you need unlimited uploads you can simply change max_num to 0 and extra to 1.
Of course that way you cannot associate one image with more then one Listing object, but if you need user to be able to delete images that is not recommended anyway.

Obfuscated Django URL for model objects

I have a Django model that looks something like this:
class Person(models.Model):
name = models.CharField(max_length=32)
place = models.ForeignKey(Place, related_name='people')
approved = models.BooleanField()
objects = PersonManager()
#models.permalink
def get_absolute_url(self):
return('deal_details', (), {
'person_slug': slugify(self.name),
})
As you could see, I already have an absolute URL for the object. However, I want to create a difficult-to-guess URL to keep track of the approval process of the object. Anyone done something similar and/or have any suggestions on how I should proceed?
My first thought was creating a model field like obfuscated_key that is generated randomly via the save function of the model. Then the URL would look something like /people/status/<id>/<obfuscated_key>/. But perhaps there's a better way to go about this?
A good way to do this would be to hash the object's ID with the installation's secret key (from settings.py). This is what the password reset email form does - there's some useful code in django.contrib.auth.tokens - and, in the very latest SVN versions, django.contrib.auth.crypto.
Something like a URL shortener (read: obfuscating) might work.
http://djangosnippets.org/snippets/1323/
I've used UUIDField to do something similar.
In the model:
uuid = UUIDField(auto=True)
then in the view check the id and uuid:
item = get_object_or_404(Item, id=id, uuid__exact=uuid)
The UUIDField is from http://djangosnippets.org/snippets/335/

Better to save a slug to the DB or generate dynamically?

I am working on a django project and would like to include a slug at the end of the url, as is done here on stackoverflow.com: http://example.com/object/1/my-slug-generated-from-my-title
The object ID will be used to look up the item, not the slug -- and, like stackoverflow.com, the slug won't matter at all when getting the link (just in displaying it).
Qestion: is there a downside (or upside) to generating the slug dynamically, rather than saving it as an actual database field ?
For example (not real code):
class Widget(models.Model):
title = models.CharField()
def _slug(self):
return slugify(self.title)
slug = property(_slug)
Rather than using an something like an AutoSlugField (for example) ?
Since my plan is to have it match the title, I didn't know if it made sense to have a duplicate field in the database.
Thanks!
If you're using the slug for decorative (rather than lookup) purposes, generating it dynamically is the best idea.
Additionally, the code sample you posted can be written like this:
#property
def slug(self):
return slugify(self.title)
Try making a slug out of the word "café" or "浦安鉄筋家族".
Chances are that it'll look like poo, unless you're really well-prepared.
Sometimes you need the ability to customize slugs.
The downside would be that you're automatically generating the slug every time you render the page. The upside is that you're not taking up space in the database with a field that will never be directly queried against.
Either way is fine, it just depends on your performance vs. space requirements.
The main downside of generating slugs dynamically is that you miss the ability to customize slugs per-object, eg. make them shorter and prettier. For English titles this can be OK, but for non-English content generated slugs can be ugly.