Mezzanine and get_FOO_display - django

I recently upgraded from Django Mezzanine from 1.4 to 3.1.4. The transition has been smooth except an error with models which extend the Mezzanine Page class. When I call the get_FOO_display property on any choice field, I get the short name with & between each character. For instance, if I have the test class:
from mezzanine.pages.models import Page
class TestModel(Page):
CHOICES = (
('ab', "Aardvarks and Bubblegum"),
('cd', "Coocoos and Diphtheria"),
)
prop = models.CharField(max_length=2, choices=CHOICES)
I get the following in the Django shell:
In [1]: from project.models import TestModel
In [2]: test = TestModel(prop="ab")
In [3]: test.get_prop_display()
Out[3]: u'a & b'
If I my model simply extends models.Model instead of Page, get_prop_display() works as expected and I get Out[3]: Aardvarks and Bubblegum
Any insights are appreciated.

update
It has been fixed.
Ref to the code:
def contribute_to_class(self, cls, name):
def _get_FIELD_display(self, field):
value = getattr(self, field.attname)
value = force_text(" & ".join([dict(field.choices).get(v, v)
for v in value]), strings_only=True)
return value
setattr(cls, '_get_FIELD_display', _get_FIELD_display)
super(MultiChoiceField, self).contribute_to_class(cls, name)
MultiChoiceField unconditionally overrides the _get_FIELD_display method of the model it resides in. In your code, that model is Page and the field is Page.in_menus.
Comparing with Django's logic, the above code may cause incorrect behavior when Page or MultiChoiceField is used in your model.
Perhaps it's a bug, and here I've raised an issue. It's fixed now.

Related

Why in django-import-export doesn't work use_bulk?

I use django-import-export 2.8.0 with Oracle 12c.
Line-by-line import via import_data() works without problems, but when I turn on the use_bulk=True option, it stops importing and does not throw any errors.
Why does not it work?
resources.py
class ClientsResources(resources.ModelResource):
class Meta:
model = Clients
fields = ('id', 'name', 'surname', 'age', 'is_active')
batch_size = 1000
use_bulk = True
raise_errors = True
views.py
def import_data(request):
if request.method == 'POST':
file_format = request.POST['file-format']
new_employees = request.FILES['importData']
clients_resource = ClientsResources()
dataset = Dataset()
imported_data = dataset.load(new_employees.read().decode('utf-8'), format=file_format)
result = clients_resource.import_data(imported_data, dry_run=True, raise_errors=True)
if not result.has_errors():
clients_resource.import_data(imported_data, dry_run=False)
return HttpResponseRedirect(request.META.get('HTTP_REFERER'))
data.csv
id,name,surname,age,is_active
18,XSXQAMA,BEHKZFI,89,Y
19,DYKNLVE,ZVYDVCX,20,Y
20,GPYXUQE,BCSRUSA,73,Y
21,EFHOGJJ,MXTWVST,93,Y
22,OGRCEEQ,KJZVQEG,52,Y
--UPD--
I used django-debug-toolbar and saw a very strange behavior with import-queries.
With Admin Panel doesnt work. I see all importing rows, but next it writes "Import finished, with 5 new and 0 updated clients.", and see this strange queries
Then I use import by my form and here simultaneous situation:
use_bulk by django-import-export (more)
And for comparing my handle create_bulk()
--UPD2--
I've tried to trail import logic and look what I found:
import_export/resources.py
def bulk_create(self, using_transactions, dry_run, raise_errors, batch_size=None):
"""
Creates objects by calling ``bulk_create``.
"""
print(self.create_instances)
try:
if len(self.create_instances) > 0:
if not using_transactions and dry_run:
pass
else:
self._meta.model.objects.bulk_create(self.create_instances, batch_size=batch_size)
except Exception as e:
logger.exception(e)
if raise_errors:
raise e
finally:
self.create_instances.clear()
This print() showed empty list in value.
This issue appears to be due to a bug in the 2.x version of django-import-export. It is fixed in v3.
The bug is present when running in bulk mode (use_bulk=True)
The logic in save_instance() is finding that 'new' instances have pk values set, and are then incorrectly treating them as updates, not creates.
I cannot determine how this would happen. It's possible this is related to using Oracle (though I cannot see how).

Flask-Babel convert Flask-WTF SelectField

I want to convert Flask-WTF SelectField value with Flask-Babel.
Here is the snippet of my code:
from flask_babel import _, lazy_gettext as _l
class PaymentStatus(enum.Enum):
REJECTED = 'REJECTED'
COMPLETED = 'COMPLETED'
EXPIRED = 'EXPIRED'
def __str__(self):
return self.value
payment_status = [(str(y), y) for y in (PaymentStatus)]
def course_list():
return Course.query.all()
class PaymentForm(FlaskForm):
course_name = QuerySelectField(_l('Course name'), validators=[required()], query_factory=course_list)
status_of_payment = SelectField(_l('Payment Status'), choices=payment_status)
# ...
# ...
There, I want to localization the SelectField choices value and QuerySelectField query_factory value with Flask-Babel.
Is it possible..?, if so, any example or refer tutorial would be appreciated :)
The SelectField choices could be handled by lazy_gettext().
Quote from The Flask Mega-Tutorial Part XIII: I18n and L10n
Some string literals are assigned outside of a request, usually when the application is starting up, so at the time these texts are evaluated there is no way to know what language to use.
Flask-Babel provides a lazy evaluation version of _() that is called lazy_gettext().
from flask_babel import lazy_gettext as _l
class LoginForm(FlaskForm):
username = StringField(_l('Username'), validators=[DataRequired()])
# ...
For choices
from flask_babel import _, lazy_gettext as _l
class PaymentStatus(enum.Enum):
REJECTED = _l('REJECTED')
COMPLETED = _l('COMPLETED')
EXPIRED = _l('EXPIRED')
def __str__(self):
return self.value
QuerySelectField query_factory accepts values queried from the database. These values should not be handled by Flask-Babel/babel. Cause the database stores data outside the Python source code.
Possible solutions:
Add a translation field in the database table and update the translation manually. Or
Using a Third-Party Translation Service on the webpage and handle it by AJAX
BTW, The Flask Mega-Tutorial made by Miguel Grinberg is a very famous Flask tutorial. All these situations are included in it.

ModelForm clean_xxxx() works for CharField, not for URLField. Django 1.5

How can I remove whitespace, prior to validation of a URLField?
Using "clean_[fieldname]()" would seem to be the documented way from https://docs.djangoproject.com/en/dev/ref/forms/validation/ , but it does not work for the URLField. I've reduced it to a basic test case which can be run in the django shell:
class XXXTestModel(models.Model):
url = models.URLField('URL',null=True,blank=True)
name = models.CharField(max_length=200)
class XXXTestForm(ModelForm):
def clean_url(self):
return self.cleaned_data['url'].strip()
def clean_name(self):
return self.cleaned_data['name'].strip()
class Meta:
model = XXXTestModel
fields = (
'url',
)
Tested from the Django shell with:
>>> django.VERSION
(1, 5, 1, 'final', 0)
>>> from xxx import XXXTestForm,XXXTestModel
>>> data = dict(url=' http://www.example.com/ ',name=' example ')
>>> f=XXXTestForm(data)
>>> f.is_valid();f.errors
False
{'url': [u'Enter a valid URL.']}
>>> f.cleaned_data
{'name': example'}
There are a number of close dupes of this question on stack overflow, but none of the answers guide toward a solution.
The issue here is how the django.forms.URLField works.
django.forms.Field.clean is defined as:
def clean(self, value):
"""
Validates the given value and returns its "cleaned" value as an
appropriate Python object.
Raises ValidationError for any errors.
"""
value = self.to_python(value)
self.validate(value)
self.run_validators(value)
return value
Note that to_python is performed before any validation. This is the issue here - django.forms.URLField can't understand the value you're giving it, so the value it produces fails the set of validators already defined as part of django.forms.URLField (namely, django.core.validators.URLValidator).
The reason it fails is django tries to "normalize" the URL. This includes things such as adding "http://" where needed. When given your example url, " http://www.example.com ", django uses urlparse.urlsplit to get it "parts" of the url. The leading space, however, messes it up and the entire value becomes part of the path. As such, django finds no scheme, and reconstitutes the URL as "http:// http://www.example.com ". This is then given to django.core.validators.URLValidator, which obviously fails.
To avoid this, we'll need to define our own URLField for our form
from django import forms
class StrippedURLField(forms.URLField):
def to_python(self, value):
return super(StrippedURLField, self).to_python(value and value.strip())
Using this ensures the process will all go as expected, and we wont need a clean_url method. (note: you should use clean_* where possible, but here it is not)
class XXXTestForm(forms.ModelForm):
url = StrippedURLField(blank=True, null=True)

Django cms accessing extended property

I've extended the Django cms Page model into ExtendedPage model and added page_image to it.
How can I now acces the page_image property in a template.
I'd like to access the page_image property for every child object in a navigation menu... creating a menu with images...
I've extended the admin and I have the field available for editing (adding the picture)
from django.db import models
from django.utils.translation import ugettext_lazy as _
from cms.models.pagemodel import Page
from django.conf import settings
class ExtendedPage(models.Model):
page = models.OneToOneField(Page, unique=True, verbose_name=_("Page"), editable=False, related_name='extended_fields')
page_image = models.ImageField(upload_to=settings.MEDIA_ROOT, verbose_name=_("Page image"), blank=True)
Thank you!
BR
request.current_page.extended_fields.page_image
should work if you are using < 2.4. In 2.4 they introduced a new two page system (published/draft) so you might need
request.current_page.publisher_draft.extended_fields.page_image
I usually write some middleware or a template processor to handle this instead of doing it repetitively in the template. Something like:
class PageOptions(object):
def process_request(self, request):
request.options = dict()
if not request.options and request.current_page:
extended_fields = None
try:
extended_fields = request.current_page.extended_fields
except:
try:
custom_settings = request.current_page.publisher_draft.extended_fields
except:
pass
if extended_fields:
for field in extended_fields._meta.fields:
request.options[field.name] = getattr(extended_fields, field.name)
return None
will allow you to simply do {{ request.options.page_image }}

Get comments for object using one query

Is it possible to get object with comments related to it? Right now django comment framework creates query for every object which has related comments and another queries for comments owners. Can I somehow avoid this? I use django 1.4 so prefetch_related is allowed.
You could create a function that caches the count:
from django.contrib.contenttypes.models import ContentType
from django.contrib import comments
def get_comment_count_key(model):
content_type = ContentType.objects.get_for_model(model)
return 'comment_count_%s_%s' % (content_type.pk, model.pk)
def get_comment_count(model):
key = get_comment_count_key(model)
value = cache.get(key)
if value is None:
value = comments.get_model().objects.filter(
content_type = ContentType.objects.get_for_model(model),
object_pk = model.pk,
site__pk = settings.SITE_ID
).count()
cache.set(key, value)
return value
You could extend the Comment model and add get_comment_count there. Or put get_comment_count as a template filter. It doesn't matter.
Of course, you would also need cache invalidation when a new comment is posted:
from django.db.models import signals
from django.contrib import comments
def refresh_comment_count(sender, instance, **kwargs):
cache.delete(get_comment_count_key(instance.content_object))
get_comment_count(instance.content_object)
post_save.connect(refresh_comment_count, sender=comments.get_model())
post_delete.connect(refresh_comment_count, sender=comments.get_model())
You could improve this last snippet, by using cache.incr() on comment_was_posted, and cache.decr() on post_delete but that's left as an exercise for you :)