Limit ForeignKey models by count - python

This is my model and I want to limit the number of photos that a user can upload just 10. I want to do it one place so it works in the admin and user facing forms. Can someone help me out here?
class StarPhotos(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
PHOTO_CATEGORY = (
('HS', "Head Shot"),
('WP', "Western Party Wear"),
('IP', "Indian Party Wear"),
('SW', "Swim Wear"),
('CW', "Casual Wear"),
)
category = models.CharField(max_length=2, choices=PHOTO_CATEGORY, default='CW')
# This FileField should preferaby be changed to ImageField with pillow installed.
photos = models.FileField(max_length=200, upload_to='images/',)
def __str__(self):
return "Images for {0}".format(self.user)

You can use a checker function in your model to check whether User has uploaded 10 photos or not
def check_photo_count(self, user):
photo_count = self.object.filter(user=user).count()
return photo_count
This does not seem to be the best solution available but it should work.
Also remember to put a check for this in your views or admin. You can also return a Boolean value from this function saying that this user is allowed to upload more photos or not.
This same logic can be applied to manager if you don't want to put checks everywhere.
So you just have to put this check in the create method and if the check fails simply raise a error or return a false value saying that object is not created.

You can override save and bulk_create methods from StarPhotos, I don't check the code, but it's some like that:
class CheckPhotoModelManager(models.Manager):
def bulk_create(self, objs, batch_size=None):
photos = StarPhotos.object.filter(user=objs[0].user).count()
if photos < 10:
super(StarPhotos, self).bulk_create(...)
class StarPhotos(models.Model):
objects = CheckPhotoModelManager()
def save(self, *args, **kwargs):
photos = StarPhotos.object.filter(user=self.user).count()
if photos < 10:
super(StarPhotos, self).save(*args, **kwargs)

Related

How to resolve name error on variable in class

I am making my first model, and I'm creating an upload system which uploads to a folder with the name of the user uploading it.
For some reason, I get this error when I try to create an object from the model:
NameError at /admin/tracks/track/add/
name '_Track__user_name' is not defined
Here's my models.py
from django.core.exceptions import ValidationError
from django.db import models
from django.core.files.images import get_image_dimensions
# Create your models here.
class Track(models.Model):
user_name = "no_user"
def get_username():
user_name = "no_user"
if request.user.is_authenticated():
user_name = request.user.username
else:
user_name = "DELETE"
def generate_user_folder_tracks(instance, filename):
return "uploads/users/%s/tracks/%s" % (user_name, filename)
def is_mp3(value):
if not value.name.endswith('.mp3'):
raise ValidationError(u'You may only upload mp3 files for tracks!')
def generate_user_folder_art(instance, filename):
return "uploads/users/%s/art/%s" % (user_name, filename)
def is_square_png(self):
if not self.name.endswith('.png'):
raise ValidationError("You may only upload png files for album art!")
else:
w, h = get_image_dimensions(self)
if not h == w:
raise ValidationError("This picture is not square! Your picture must be equally wide as its height.")
else:
if not (h + w) >= 1000:
raise ValidationError("This picture is too small! The minimum dimensions are 500 by 500 pixels.")
return self
# Variables
track_type_choices = [
('ORG', 'Original'),
('RMX', 'Remix'),
('CLB', 'Collab'),
('LIV', 'Live'),
]
# Model Fields
name = models.CharField(max_length=100)
desc = models.TextField(max_length=7500)
track_type = models.CharField(max_length=3,
choices=track_type_choices,
default='ORG')
track_type_content = models.CharField(max_length=100,blank=True)
created = models.TimeField(auto_now=True,auto_now_add=False)
upload = models.FileField(upload_to=generate_user_folder_tracks,validators=[is_mp3])
albumart = models.ImageField(upload_to=generate_user_folder_art,validators=[is_square_png])
As you can see from the first line after the class is defined, there is clearly a variable called "user_name", and when using my upload functions, it is supposed to use this variable for the folder name.
I am very confused to why this is throwing an error, what am I doing wrong?
You have some serious problems with variable scope here. Just defining an attribute called "user_name" at the top of the class does not automatically give you access to it elsewhere in the class; you would need to access it via the class itself. Usually you do that through the self variable that is the first parameter to every method.
However, many of your methods do not even accept a self parameter, so they would give TypeError when they are called. On top of that, your user_name attribute is actually a class attribute, which would be shared by all instances of User - this would clearly be a bad thing. You should really make it a Django field, like the other attributes.
Finally, your scope issues worsen when you try and access request in one of those methods. Again, you can't access a variable unless it has been passed to that method (or is available in global scope, which the request is definitely not). So get_username cannot work at all.
I must say though that all that is irrelevant, as the error you get does not even match your code: you must have accessed Track.__user_name somewhere to get that error.
You do have a variable username, but its not a field which would mean that the query set it looks like you're creating won't find it
user_name = "no_user"
should be one of the following
user_name = models.CharField(default='no_user')
user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True)
The only reason I've suggested a CharField here is incase you don't use some form of authorisation user model in your app. If you do, then you should use a foreign key to that model.

Overiding save for just one field in Django

This is my models.py:
class College(models.Model):
name = models.CharField(unique=True, max_length=50,
help_text='Name of the college.'
)
slug = models.SlugField(unique=True)
description = models.TextField(blank = True)
image = models.ImageField(upload_to='site-media/media/college_images/',
default = 'site-media/media/college_images/default.jpeg'
)
user = models.ForeignKey(User)
def get_absolute_url(self):
return "/%s/" % self.slug
def create_thumbnail(self):
if not self.image:
return
THUMBNAIL_SIZE = (250,193)
image = Image.open(StringIO(self.image.read()))
thumb = ImageOps.fit(image, THUMBNAIL_SIZE, Image.ANTIALIAS)
temp_handle = StringIO()
thumb.convert('RGB').save(temp_handle, 'jpeg')
temp_handle.seek(0)
suf = SimpleUploadedFile(os.path.split(self.image.name)[-1],
temp_handle.read(), content_type='image/jpeg')
self.image.save('%s_college_.%s'%(os.path.splitext(suf.name)[0],'jpeg'), suf, save=False)
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
self.create_thumbnail()
super(College, self).save(*args, **kwargs)
I have presented the user with a form to edit just the description. When the description 'POST' is made the 'save()' method above is called. The problem with this is that the thumbnail is created over and over again with a bigger name every time. And, also the previous thumbnail is not deleted from the hard disk. Is it possible, that this 'thumbnail' method doesn't get called over and over again with each edit of the 'description'.
You can check whether you are sending image file in you request post or not. For this You need to call your save in view with one argument request like : college.save(request)
def save(self, request=False, *args, **kwargs):
self.slug = slugify(self.name)
if request and request.FILES.get('image',False):
self.create_thumbnail()
super(College, self).save(*args, **kwargs)
OR
you can differentiate your save and edit using
if self.pk is not None
But it can create problem if you edit your image.
So its your choice How you want to go with it.
There are two reasonable paths I see to handle this. Neither are ideal, so I'll be interested to see if anyone has a better option to offer.
One is to save the filename of the most recent image for which you created a thumbnail as a model field. Then in your save method you can check the filename of your image field against it and create a new thumbmail if it has changed. This has the disadvantage of requiring a new model field, but is more universal in its application.
The other is to override the save method of the form class. You can then check the old image filename by looking at self.instance.image and comparing that against self.cleaned_data['image']. This has the disadvantage of only affecting views that use that form class, but doesn't require changing your data model. You can pass a custom form to the admin, if you're using it, or override the admin class's save_model method.
The simple solution is to NOT try to create the thumbnail at this stage, but only when it's needed, ie (pseudocode example):
class Whatever(models.Model):
image = models.ImageField(...)
#property
def thumbnail(self):
thumb = do_i_have_a_thumbnail_yet(self.image)
if not thumb:
thumb = ok_then_make_me_a_thumbnail_and_store_it_please(self.image)
return thumb
Implementing do_i_have_a_thumbnail_yet() and ok_then_make_me_a_thumbnail_and_store_it_please() is left as an exercise to the reader, but there are librairies or django apps providing such services.
Another - and even better imho - solution is to delegate thumbnails handling to a templatetag or template filter. Why ? because
thumbnails are mostly presentation stuff and do not belong to the model
most of the time you'll need different thumbnails sizes from a same image
the front-end developer may want to be free to change the thumbnail's size without having to ask you to change your backend code and write a migration script to recompute all existing thumbnails.

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

django admin - populate field with callable

I can't find a single example of anyone doing this apart from this example, which doesn't help me other than to know where the code needs to sit.
How to prepopulate UserProfile fields in the Django admin?
so this is my code
class QuoteMaterial(models.Model):
name = models.CharField(_('name'), max_length=255)
content = models.TextField(_('content'),
help_text=_('A static priced item used when doing a job. Selectable when creating a quote. '))
price = models.DecimalField(_('price'), max_digits=6, help_text="not sure if this is before or after VAT yet", decimal_places=2, default="0.00")
def get_companies():
return CompanyProfile.objects.filter(user=request.user)
company = models.ForeignKey(CompanyProfile, default=get_companies)
If its not obvious, im trying in the admin section to populate a dropdown with the available companies that belong to the user that is logged in.
my problem is that i dont know how to pass the request object to "get_companies". anyone know of any examples.
You will have to do this overriding in your admin class that extends the ModelAdmin, not in your class that extends models.Model. Specifically, you need to override formfield_for_foreignkey.
From the docs:
class MyModelAdmin(admin.ModelAdmin):
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "car":
kwargs["queryset"] = Car.objects.filter(owner=request.user)
return super(MyModelAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
For your case, it would seem like:
if db_field.name == "company":
kwargs['queryset'] = request.user.company_set.all()
You're mixing up terms.
"Prepopulating" means to fill in a field from another field. It's not how you filter things for the admin popups, since you aren't actually setting the field, but simply limiting choices and letting the user set the field from those.
Aditionally, the default value for a field needs to be a constant, since this is passed down to the database, which can't use a query to set a default.
What you really want is something like the limit_choices_to (docs) parameter for your ForeignKey, but even then, you can't use request for this; it has to work using fields in the model. The reason for this is that, if you based it on the user, then some users would be unable to select the current value set by another user. You don't want company changing itself when the user just wants to change content, for example, just because user doesn't yield the current company in the filter.

Inline-like solution for Django Admin where Admin contains ForeignKey to other model

I have several Customers who book Appointments. Each Appointment has exactly one customer, though a customer can be booked for multiple appointments occurring at different times.
class Customer(model.Model):
def __unicode__(self):
return u'%s' % (self.name,)
name = models.CharField(max_length=30)
# and about ten other fields I'd like to see from the admin view.
class Appointment(models.Model):
datetime = models.DateTimeField()
customer = models.ForeignKey("Customer")
class Meta:
ordering = ('datetime',)
Now when an admin goes to browse through the schedule by looking at the Appointments (ordered by time) in the admin, sometimes they want to see information about the customer who has a certain appointment. Right now, they'd have to remember the customer's name, navigate from the Appointment to the Customer admin page, find the remembered Customer, and only then could browse their information.
Ideally something like an admin inline would be great. However, I can only seem to make a CustomerInline on the Appointment admin page if Customer had a ForeignKey("Appointment"). (Django specifically gives me an error saying Customer has no ForeignKey to Appointment). Does anyone know of a similar functionality, but when Appointment has a ForeignKey('Customer')?
Note: I simplified the models; the actual Customer field currently has about ~10 fields besides the name (some free text), so it would be impractical to put all the information in the __unicode__.
There is no easy way to do this with django. The inlines are designed to follow relationships backwards.
Potentially the best substitute would be to provide a link to the user object. In the list view this is pretty trivial:
Add a method to your appointment model like:
def customer_admin_link(self):
return 'Customer' % reverse('admin:app_label_customer_change %s') % self.id
customer_admin_link.allow_tags = True
customer_admin_link.short_description = 'Customer'
Then in your ModelAdmin add:
list_display = (..., 'customer_admin_link', ...)
Another solution to get exactly what you're looking for at the cost of being a bit more complex would be to define a custom admin template. If you do that you can basically do anything. Here is a guide I've used before to explain:
http://www.unessa.net/en/hoyci/2006/12/custom-admin-templates/
Basically copy the change form from the django source and add code to display the customer information.
Completing #John's answer from above - define what you would like to see on the your changelist:
return '%s' % (
reverse('admin:applabel_customer_change', (self.customer.id,)),
self.customer.name # add more stuff here
)
And to add this to the change form, see: Add custom html between two model fields in Django admin's change_form
In the ModelAdmin class for your Appointments, you should declare the following method:
class MySuperModelAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
if obj:
# create your own model admin instance here, because you will have the Customer's
# id so you know which instance to fetch
# something like the following
inline_instance = MyModelAdminInline(self.model, self.admin_site)
self.inline_instances = [inline_instance]
return super(MySuperModelAdmin, self).get_form(request, obj, **kwargs)
For more information, browser the source for that function to give you an idea of what you will have access to.
https://code.djangoproject.com/browser/django/trunk/django/contrib/admin/options.py#L423
There is a library you can use it.
https://github.com/daniyalzade/django_reverse_admin
But if you want to use link to object in showing table you can like this code:
def customer_link(self, obj):
if obj.customer:
reverse_link = 'admin:%s_%s_change' % (
obj.customer._meta.app_label, obj.customer._meta.model_name)
link = reverse(reverse_link, args=[obj.customer.id])
return format_html('More detail' % link)
return format_html('<span >-</span>')
customer_link.allow_tags = True
customer_link.short_description = 'Customer Info'
And in list_display:
list_display = (...,customer_link,...)

Categories

Resources