I am upgrading to Django 1.7.4 and use the new built-in migrations, but I get the following error when attempting to run makemigrations:
ValueError: Could not find function upload_callback in app_utils.
Please note that due to Python 2 limitations, you cannot serialize unbound
method functions (e.g. a method declared and used in the same class body).
Please move the function into the main module body to use migrations.For more information,
see https://docs.djangoproject.com/en/1.7/topics/migrations/#serializing-values
My model definition:
class Discount(models.Model):
banner = models.ImageField(
help_text=_("Banner image for this discount"),
upload_to=upload_to('discounts/', 'title'),
blank=True,
null=True
)
And my upload callback in app_utils.py:
def upload_to(path, attribute=None):
def upload_callback(instance, filename):
compact = filename[:50]
try:
attr= unicode( slugify( getattr(instance, attribute) ) )
mypath = '%s/%s/%s' % (path, attr, compact)
except:
mypath = '%s%s' % (path, compact)
return mypath
return upload_callback
Based on the error message, it indicates upload_callback is unbound. So
I tried pulling upload_callback outside of the the wrapper function, but cannot find a way to pass in the extra path and attribute parameters passed into upload_to. Django's documentation is unclear how to do this, and it only specifies that instance and filename are required.
Ideally, what I would like is:
def upload_to(instance, filename, path, attribute=None):
...
Any ideas how I can go about achieving this?
After some serious deep digging on the web, I came across this Django bug report. The solution seems to create a callable factory class:
https://code.djangoproject.com/ticket/22999
More discussion here: Django - Cannot create migrations for ImageField with dynamic upload_to value
Related
I have a use case where I'm attempting to override an Image URL if it exists in our database.
Here is the section of the queryset that is grabbing the ImageField from via an F() query.
preferred_profile_photo=Case(
When(
# agent not exists
Q(agent__id__isnull=False),
then=F("agent__profile__profile_photo"),
),
default=Value(None, output_field=ImageField()),
)
The Case-When is resolving correctly, but the issue is the value returns from F("agent__profile__profile_photo") is not the URL than can be used by the UI. Instead it is something like:
"agentphoto/09bd7dc0-62f6-49ab-blah-6c57b23029d7/profile/1665342685--77e51d9c5asdf345364f774d0b2def48.jpeg"
Typically, I'd retrieve the URL via agent.profile.profile_photo.url, but I receive the following when attempting to perform preferred_profile_photo.url:
AttributeError: 'str' object has no attribute 'url'.
I've tried wrapping in Value(..., output_field=ImageField()) with no luck.
The crux here is retrieving the url from the ImageField after resolving from F()
For reference, I'm using storages.backends.s3boto3.S3Boto3Storage.
I was able to implement this using some information from this post.
Here is the helper function I used to implement:
from django.core.files.storage import get_storage_class
def get_media_storage_url(file_name: str) -> str:
"""Takes the postgres stored ImageField value and converts it to
the proper S3 backend URL. For use cases, where calling .url on the
photo field is not feasible.
Args:
file_name (str): the value of the ImageField
Returns:
str: the full URL of the object usable by the UI
"""
media_storage = get_storage_class()()
return media_storage.url(name=file_name)
Django doesn't store full URL in DB. It builds absolute url on code level. Quote from docs:
All that will be stored in your database is a path to the file (relative to MEDIA_ROOT).
You can use build_absolute_uri() method to get full URL of your files. For example you can do it on serializer level:
class YourSerializer(ModelSerializer):
preferred_profile_photo = serializers.SerializerMethodField()
class Meta:
model = YourModel
fields = [
'preferred_profile_photo',
]
def get_preferred_profile_photo(self, obj):
request = self.context.get('request')
return request.build_absolute_uri(obj.preferred_profile_photo)
When I do python manage.py makemigrations, i get above error and I am unsure where the error is happening. I saw some post regarding this issue but i find mainly in DateTimeField() where the function was passed but in my case I have used auto_now attribute instead of some datetime related function.
However, I have used lambda function in the class method as follow.
#classmethod
def get_content_models(cls):
"""
Return all Package subclasses.
"""
is_content_model = lambda m: m is not Package and issubclass(m, Package)
def set_helpers(self, context):
current_package_id = getattr(current_package, "id", None)
current_parent_id = getattr(current_package, "parent_id", None)
self.is_current_child = self.parent_id == current_package_id
self.is_child = self.is_current_child
def is_c_or_a(package_id):
parent_id = context.get("_parent_package_ids", {}).get(package_id)
return self.id == package_id or (parent_id and is_c_or_a(parent_id))
self.is_current_or_ascendant = lambda: bool(is_c_or_a(current_package_id))
I am not clear on this issue, So i have posted for understanding the cause. Is above code creating that issue? If it is the cause, what should be done instead?
I don't know where exactly this issue lies in the models so here is the detail of models.py of package app in gist because the code is a bit huge . It is not reached to booking models so I am only putting the code of package app and here it is
https://gist.github.com/SanskarSans/51d2f287309a97163e680cc38abd3e06
UPDATE
In my Package models, I have used the custom field and in that field there was the use of lambda instead of callable function and that was creating an issue. Due to the long file of models, I did not paste it here, I apologize for that.
Here what I had done
in_menus = MenusField(_("Show in menus"), blank=True, null=True)
class MenusField(MultiChoiceField):
"""
``MultiChoiceField`` for specifying which menus a package should
appear in.
"""
def __init__(self, *args, **kwargs):
choices = [t[:2] for t in getattr(settings, "PAGE_MENU_TEMPLATES", [])]
default = getattr(settings, "PAGE_MENU_TEMPLATES_DEFAULT", None)
if default is None:
default = [t[0] for t in choices]
elif not default:
default = None
if isinstance(default, (tuple, list)):
d = tuple(default)
# this lambda should be avoided
# default = lambda:d
default = default_value(d)
defaults = {"max_length": 100, "choices": choices, "default": default}
defaults.update(kwargs)
super(MenusField, self).__init__(*args, **defaults)
An example replacing the lambda with a function.
Borken version:
class SomeModel(ParentModel):
thing_to_export = ArrayField(models.CharField(max_length=50),
default=lambda: ['Default thing'])
Working version:
def default_thing():
return ['THIS IS A DEFAULT']
class SomeModel(ParentModel):
thing_to_export = ArrayField(models.CharField(max_length=50),
default=default_thing)
I assume you are using the pickle module for serialization.
You cannot pickle the class in which set_helpers is defined. That method sets self.is_current_or_ascendant to a lambda function, and those are on the list of things that cannot be pickled (see 12.1.4 in https://docs.python.org/3/library/pickle.html).
The class method cannot be a problem since it only defines one local variable, is_content_model, which immediately goes out of scope and gets deleted. In fact that method, as you presented it here, does nothing at all.
The follows might be helpful to others in the future.
It worked to change the lambda expression into a function.
The following square_root and square_root_lambda worked the same:
def square_root(x):
return math.sqrt(x)
square_root_lambda = lambda x: math.sqrt(x)
print(square_root(4))
print(square_root_lambda(4))
I'm working on a Django 1.8.2 project.
This project has multiple Django applications.
Application app_a has class MyClassA as follows:
class MyClassA(models.Model):
name = models.CharField(max_length=50, null=True, blank=True)
#staticmethod
def my_static_method():
ret_val = MyClassA.objects.filter()
return "World"
Application app_b has class MyClassB as follows:
class MyClassB(models.Model):
name = models.CharField(max_length=50, null=False, blank=False)
def my_method(self, arg1=MyClassA.my_static_method()):
return "Hello"
When I run manage.py test, it works fine.
However, then I change MyClassA.my_static_method() to the following:
#staticmethod
def my_static_method():
ret_val = MyClassA.objects.filter(name=None)
return "World"
When I do that, and then run manage.py test, it fails with the following error:
File "my-virtual-env/lib/python2.7/site-packages/django/apps/registry.py", line 131, in check_models_ready
raise AppRegistryNotReady("Models aren't loaded yet.")
django.core.exceptions.AppRegistryNotReady: Models aren't loaded yet.
Why is this happening? How do I fix this?
The only change that I made is adding the filter value name=None.
In Django you must not run queries at import time. (See here for details.)
Because default argument values in Python are evaluated when the function is defined (as opposed to when it is called), your MyClassB.my_method() definition is calling MyClassA.my_static_method(), which attempts to run a query.
Why did you see an error in one version of your code and not the other? One of them is evaluating the query (i.e. trying to access the database) and one isn't, for whatever reason. It's your responsibility to make sure that nothing you do at import time tries to access the database.
If your goal is for that default argument to be evaluated on each call, the standard idiom in Python is:
def my_method(self, arg1=None):
if arg1 is None:
arg1 = MyClassA.my_static_method()
In this case you will get an error only if you make a mistake in the manager function. I tried the same and it worked for me. but I got the same error when I made an error in the filter() .like MyClassA.objects.filter(a_field_not_in_the_model=None).
I think you have to re check your original code. check if your model is ok.
I have a model with a version number in it. I want it to self-increment when new data is posted with an existing id via TastyPie. I'm currently doing this via the hydrate method, which works as long as two users don't try to update at once:
class MyResource(ModelResource):
...
def hydrate_version(self, bundle):
if 'id' in bundle.data:
target = self._meta.queryset.get(id=int(bundle.data['id']))
bundle.data['version'] = target.version+1
return bundle
I'd like to do this more robustly by using Django's F() expressions, e.g.:
def hydrate_version(self, bundle):
if 'id' in bundle.data:
from django.db.models import F
target = self._meta.queryset.get(id=int(bundle.data['id']))
bundle.data['version'] = F('version')+1
return bundle
However, this gives me an error:
TypeError: int() argument must be a string or a number, not 'ExpressionNode'
Is there a way to more robustly increment the version number with TastyPie?
thanks!
I would override the save() method for your Django Model instead, and perform the update there. That has the added advantage of ensuring the same behavior regardless of an update from tastypie or from the django/python shell.
def save(self, *args, **kwargs):
self.version = F('version') + 1
super(MyModel, self).save(*args, **kwargs)
This has been answered at github here, though I haven't tried this myself yet. To quote from that link:
You're setting bundle.data inside a hydrate method. Usually you modify bundle.obj in hydrate methods and bundle.data in dehydrate methods.
Also, those F objects are meant to be applied to Django model fields.
I think what you want is:
def hydrate_version(self, bundle):
if bundle.obj.id is not None:
from django.db.models import F
bundle.obj.version = F('version')+1
return bundle
How do you get the model object of a tastypie modelresource from it's uri?
for example:
if you were given the uri as a string in python, how do you get the model object of that string?
Tastypie's Resource class (which is the guy ModelResource is subclassing ) provides a method get_via_uri(uri, request). Be aware that his calls through to apply_authorization_limits(request, object_list) so if you don't receive the desired result make sure to edit your request in such a way that it passes your authorisation.
A bad alternative would be using a regex to extract the id from your url and then use it to filter through the list of all objects. That was my dirty hack until I got get_via_uri working and I do NOT recommend using this. ;)
id_regex = re.compile("/(\d+)/$")
object_id = id_regex.findall(your_url)[0]
your_object = filter(lambda x: x.id == int(object_id),YourResource().get_object_list(request))[0]
You can use get_via_uri, but as #Zakum mentions, that will apply authorization, which you probably don't want. So digging into the source for that method we see that we can resolve the URI like this:
from django.core.urlresolvers import resolve, get_script_prefix
def get_pk_from_uri(uri):
prefix = get_script_prefix()
chomped_uri = uri
if prefix and chomped_uri.startswith(prefix):
chomped_uri = chomped_uri[len(prefix)-1:]
try:
view, args, kwargs = resolve(chomped_uri)
except Resolver404:
raise NotFound("The URL provided '%s' was not a link to a valid resource." % uri)
return kwargs['pk']
If your Django application is located at the root of the webserver (i.e. get_script_prefix() == '/') then you can simplify this down to:
view, args, kwargs = resolve(uri)
pk = kwargs['pk']
Are you looking for the flowchart? It really depends on when you want the object.
Within the dehydration cycle you simple can access it via bundle, e.g.
class MyResource(Resource):
# fields etc.
def dehydrate(self, bundle):
# Include the request IP in the bundle if the object has an attribute value
if bundle.obj.user:
bundle.data['request_ip'] = bundle.request.META.get('REMOTE_ADDR')
return bundle
If you want to manually retrieve an object by an api url, given a pattern you could simply traverse the slug or primary key (or whatever it is) via the default orm scheme?