AttributeError when saving MPTTModel - django

I'm trying to build a menu app in Django using django-mptt to create nested menu items. The menu items should be ordered by menu_order when the tree is built.
The problem is that whenever I add nested menu items, reorder them and save the menu, this error is raised:
'NoneType' object has no attribute 'tree_id'
To be able to save the menu I have to either manually rebuild the tree from Django shell, which does not always help, or remove the parent relation from the child items.
When removing order_insertion_by = ['menu_order'] from the MenuItem model, everything (except ordering) works as intended.
models.py:
class Menu(models.Model):
POSITIONS = Choices(('header', _('Header')), ('footer', _('Footer')))
title = models.CharField(max_length=255, default='')
position = models.SlugField(choices=POSITIONS, max_length=64, default='')
def save(self, *args, **kwargs):
MenuItem.objects.rebuild()
super(Menu, self).save(*args, **kwargs)
class MenuItem(MPTTModel):
content_type = models.ForeignKey(ContentType, blank=True, null=True)
object_id = models.PositiveIntegerField(blank=True, null=True)
linked_object = generic.GenericForeignKey()
menu = models.ForeignKey('Menu', related_name='menu_items')
parent = TreeForeignKey('self', null=True, blank=True, related_name='children')
menu_order = models.PositiveSmallIntegerField(default=0)
class MPTTMeta:
order_insertion_by = ['menu_order']
admin.py:
class MenuItemInline(admin.StackedInline):
model = MenuItem
extra = 0
sortable_field_name = 'menu_order'
autocomplete_lookup_fields = {
'generic': [['content_type', 'object_id']]
}
def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
field = super(MenuItemInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
if db_field.name == 'parent':
if request._obj_ is not None:
field.queryset = field.queryset.filter(menu=request._obj_)
else:
field.queryset = field.queryset.none()
return field
class MenuAdmin(admin.ModelAdmin):
inlines = (MenuItemInline,)
def get_form(self, request, obj=None, **kwargs):
request._obj_ = obj
return super(MenuAdmin, self).get_form(request, obj, **kwargs)
admin.site.register(Menu, MenuAdmin)
The traceback:
Traceback:
File "/.../django/core/handlers/base.py" in get_response
115. response = callback(request, *callback_args, **callback_kwargs)
File "/.../django/contrib/admin/options.py" in wrapper
372. return self.admin_site.admin_view(view)(*args, **kwargs)
File "/.../django/utils/decorators.py" in _wrapped_view
91. response = view_func(request, *args, **kwargs)
File "/.../django/views/decorators/cache.py" in _wrapped_view_func
89. response = view_func(request, *args, **kwargs)
File "/.../django/contrib/admin/sites.py" in inner
202. return view(request, *args, **kwargs)
File "/.../django/utils/decorators.py" in _wrapper
25. return bound_func(*args, **kwargs)
File "/.../django/utils/decorators.py" in _wrapped_view
91. response = view_func(request, *args, **kwargs)
File "/.../django/utils/decorators.py" in bound_func
21. return func(self, *args2, **kwargs2)
File "/.../django/db/transaction.py" in inner
223. return func(*args, **kwargs)
File "/.../django/contrib/admin/options.py" in change_view
1106. self.save_related(request, form, formsets, True)
File "/.../django/contrib/admin/options.py" in save_related
764. self.save_formset(request, form, formset, change=change)
File "/.../django/contrib/admin/options.py" in save_formset
752. formset.save()
File "/.../django/forms/models.py" in save
514. return self.save_existing_objects(commit) + self.save_new_objects(commit)
File "/.../django/forms/models.py" in save_existing_objects
634. saved_instances.append(self.save_existing(form, obj, commit=commit))
File "/.../django/forms/models.py" in save_existing
502. return form.save(commit=commit)
File "/.../django/forms/models.py" in save
370. fail_message, commit, construct=False)
File "/.../django/forms/models.py" in save_instance
87. instance.save()
File "/.../mptt/models.py" in save
794. self._tree_manager._move_node(self, rightmost_sibling, 'right', save=False)
File "/.../mptt/managers.py" in _move_node
414. self._make_sibling_of_root_node(node, target, position)
File "/.../mptt/managers.py" in _make_sibling_of_root_node
769. new_tree_id = getattr(right_sibling, self.tree_id_attr)
Exception Type: AttributeError at /admin/menus/menu/2/
Exception Value: 'NoneType' object has no attribute 'tree_id'
'NoneType' refers to right_sibling which is None.
The cause traces back to three lines above, where right_sibling is set:
right_sibling = target.get_next_sibling()
get_next_sibling returns None even though there is a next sibling.
When reordering the two last menu items, I sometimes end up with two root nodes with the same tree_id, lft, and rght values. This is causing the get_next_sibling function to query for a node where tree_id__gt=4 when the last two nodes both have a tree_id of 4.
The MenuItem objects are managed with an inline admin on every Menu object. They can be reordered using Grappelli's sortable inlines. There seems to be an issue with newly created child nodes getting a higher menu_order value than the following root nodes.
I'm using Python 2.7.4, Django 1.5.5, and django-mptt 0.6.0.
Is this a bug in django-mptt or am I doing something wrong?

I solved this by adding another sorting attribute and rebuilding the menu when the formset is saved. This is probably not the most optimal solution, every save now takes a few extra seconds.
models.py:
class MenuItem:
parent_order = models.PositiveSmallIntegerField(default=0)
def save(self, **kwargs):
if self.parent:
self.parent_order = self.parent.menu_order
super(MenuItem, self).save(**kwargs)
class MPTTMeta:
order_insertion_by = ['parent_order', 'menu_order']
admin.py:
class MenuAdmin(MarketRelatedAdmin):
def save_formset(self, request, form, formset, change):
formset.save()
MenuItem.objects.rebuild()

I solved this by adding two lines to the TreeManager.
def _make_sibling_of_root_node(self, node, target, position):
...
elif position == 'right':
if target_tree_id > tree_id:
new_tree_id = target_tree_id
lower_bound, upper_bound = tree_id, target_tree_id
shift = -1
else:
right_sibling = target.get_next_sibling()
if node == right_sibling:
return
# Addition
if not right_sibling:
return
# end of addition
new_tree_id = getattr(right_sibling, self.tree_id_attr)
lower_bound, upper_bound = new_tree_id, tree_id
shift = 1

Related

How to set initial value of a field using foreign field of a model in django?

I wish to set initial value in profile form.
Profile model has a OneToOne relation with User model.
I get the following error when I set initial field in init method,
'ForwardOneToOneDescriptor' object has no attribute 'email'
Traceback
File
"/home/drogon/Crowdsocial_project/venv_ubuntu/lib/python3.6/site-packages/django/core/handlers/exception.py" in inner
34. response = get_response(request)
File
"/home/drogon/Crowdsocial_project/venv_ubuntu/lib/python3.6/site-packages/django/core/handlers/base.py"
in _get_response
126. response = self.process_exception_by_middleware(e, request)
File
"/home/drogon/Crowdsocial_project/venv_ubuntu/lib/python3.6/site-packages/django/core/handlers/base.py"
in _get_response
124. response = wrapped_callback(request, *callback_args, **callback_kwargs)
File
"/home/drogon/Crowdsocial_project/venv_ubuntu/lib/python3.6/site-packages/django/views/generic/base.py"
in view
68. return self.dispatch(request, *args, **kwargs)
File
"/home/drogon/Crowdsocial_project/venv_ubuntu/lib/python3.6/site-packages/django/views/generic/base.py"
in dispatch
88. return handler(request, *args, **kwargs)
File
"/home/drogon/Crowdsocial_project/venv_ubuntu/lib/python3.6/site-packages/django/views/generic/edit.py"
in get
190. return super().get(request, *args, **kwargs)
File
"/home/drogon/Crowdsocial_project/venv_ubuntu/lib/python3.6/site-packages/django/views/generic/edit.py"
in get
133. return self.render_to_response(self.get_context_data())
File "/home/drogon/Crowdsocial_project/users/views.py" in
get_context_data
90. context = super(ProfileSettingsView, self).get_context_data(**kwargs)
File
"/home/drogon/Crowdsocial_project/venv_ubuntu/lib/python3.6/site-packages/django/views/generic/edit.py"
in get_context_data
66. kwargs['form'] = self.get_form()
File
"/home/drogon/Crowdsocial_project/venv_ubuntu/lib/python3.6/site-packages/django/views/generic/edit.py"
in get_form
33. return form_class(**self.get_form_kwargs())
File "/home/drogon/Crowdsocial_project/users/forms.py" in init
144. self.initial['profile_email'] = Profile.user.email
Exception Type: AttributeError at /users/12/profile/settings Exception
Value: 'ForwardOneToOneDescriptor' object has no attribute 'email'
Profile Model
class Profile(models.Model):
user = models.OneToOneField(CustomUser, on_delete=models.CASCADE, null=True, blank=True)
full_name = models.CharField(max_length=30, null=True, blank=True)
Profile Form
class ProfileSettingsForm(forms.ModelForm):
full_name = forms.CharField(required=False,
widget=forms.TextInput(attrs={'readonly': 'readonly'}))
profile_email = forms.CharField(required=False,
widget=forms.TextInput(attrs={'readonly': 'readonly'}))
def __init__(self, *args, **kwargs):
super(ProfileSettingsForm, self).__init__(*args, **kwargs)
self.initial['profile_email'] = Profile.user.email
self.initial['full_name'] = Profile.user.name
class Meta:
model = Profile
fields = ['image','full_name','biography','profile_email','linked_in','facebook',
'twitter','phone','education']

Generic detail viewResult must be called with either an object pk or a slug

I got this error shown as title. According to document, I added "slug_url_kwarg = 'result'", (result is the app name). It doesn't work, then I added "def get_object(self):", it returns the error as "id is a KeyError"
The database it has created by itself a primary ket:"id", so I used this "id" as slug_field. Is this correct?
Do you think if it is necessary for me to use UpdateView? I want the html to display a result after submiting the form(using ajax)?
Thans in advance for your reply.
url
url(r'^result_list/$',ResultView.as_view(),name='result'),
views.py
class ResultView(UpdateView):
context_object_name = 'result_list'
template_name = 'result_list.html'
form_class = InputForm
slug_field = 'result.id' ---here means the pk,right?
slug_url_kwarg = 'result'
def get_queryset(self):
return Result.objects.all()
def post(self, request, *args, **kwargs):
form = InputForm(request.POST)
if form.is_valid():
if self.request.is_ajax():
company = form.cleaned_data['company']
region = form.cleaned_data['region']
queryset=Result.objects.filter(region=region)
return HttpResponse(simplejson.dumps(to_json),mimetype='application/json')
else:
return HttpResponse(form.errors)
def get_context_data(self, **kwargs):
context = super(ResultView, self).get_context_data(**kwargs)
context["sales"] = self.get_queryset().aggregate(Sum('sales'))
def get_object(self):
object = get_object_or_404(Result,id=self.kwargs['id']) <!here is not correct--->
return object
traceback
File "C:\Python27\lib\site-packages\django-1.8.3-py2.7.egg\django\core\handlers\base.py" in get_response
132. response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "C:\Python27\lib\site-packages\django-1.8.3-py2.7.egg\django\views\generic\base.py" in view
71. return self.dispatch(request, *args, **kwargs)
File "C:\Python27\lib\site-packages\django-1.8.3-py2.7.egg\django\views\generic\base.py" in dispatch
89. return handler(request, *args, **kwargs)
File "C:\Python27\lib\site-packages\django-1.8.3-py2.7.egg\django\views\generic\edit.py" in get
267. self.object = self.get_object()
File "C:\Users\user\Desktop\project\result\views.py" in get_object
54. object = get_object_or_404(Result,id=self.kwargs['id'])
Exception Type: KeyError at /result_list/
Exception Value: 'id'
You forgot to map the url pattern, you don't need to specify slug_field and slug_url_kwarg in your case:
url(r'^dupont_list/(?P<pk>[0-9]+)/$', DupontView.as_view(), name='dupont'),

Django: Adding "created by" field to object

I need to store the user who is creating a specific object in the database, like so:
(models.py)
class Address(models.Model):
createdBy = models.ForeignKey(User)
address1 = models.CharField("Address Line 1", max_length=40)
address2 = models.CharField("Address Line 2", max_length=40, blank=True)
city = models.CharField(max_length=20)
state = models.CharField(max_length=3)
zipCode = models.CharField("Postal Code", max_length=10)
My view for handling the form is here (views.py):
def handlePopAdd(request, addForm, field):
if request.method == "POST":
form = addForm(request.POST)
if form.is_valid():
try:
newObject = form.save(request)
except forms.ValidationError, error:
newObject = None
if newObject:
return HttpResponse('<script type="text/javascript">opener.dismissAddAnotherPopup(window, "%s", "%s");</script>'% (escape(newObject._get_pk_val()), escape(newObject)))
else:
form = addForm()
pageContext = {'form': form, 'field': field}
return render_to_response("address_popup.html", pageContext, context_instance=RequestContext(request))
And finally, here is my form code: (forms.py)
class AddressForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
return super(AddressForm, self).__init__(*args, **kwargs)
def save(self, *args, **kwargs):
obj = super(AddressForm, self).save(commit=False)
if self.request:
obj.createdBy = self.request.user
obj.save()
class Meta:
model = Address
exclude = ('createdBy',)
I am using the top answer suggested by this question, and have hit a wall after a few hours staring a hole through this.
Here's my traceback. Error is IntegrityError at /app/address/add/ (1048, "Column 'createdBy_id' cannot be null")
Traceback:
File "/usr/local/lib/python2.6/dist-packages/django/core/handlers/base.py" in get_response
111. response = callback(request, *callback_args, **callback_kwargs)
File "/usr/local/lib/python2.6/dist-packages/django/contrib/auth/decorators.py" in _wrapped_view
23. return view_func(request, *args, **kwargs)
File "/home/matthew/lessonhelper/../lessonhelper/lessons/views.py" in address_add
78. return handlePopAdd(request, AddressForm, 'address')
File "/usr/local/lib/python2.6/dist-packages/django/contrib/auth/decorators.py" in _wrapped_view
23. return view_func(request, *args, **kwargs)
File "/home/matthew/lessonhelper/../lessonhelper/lessons/views.py" in handlePopAdd
66. newObject = form.save(request)
File "/home/matthew/lessonhelper/../lessonhelper/lessons/forms.py" in save
29. obj.save()
File "/usr/local/lib/python2.6/dist-packages/django/db/models/base.py" in save
460. self.save_base(using=using, force_insert=force_insert, force_update=force_update)
File "/usr/local/lib/python2.6/dist-packages/django/db/models/base.py" in save_base
553. result = manager._insert(values, return_id=update_pk, using=using)
File "/usr/local/lib/python2.6/dist-packages/django/db/models/manager.py" in _insert
195. return insert_query(self.model, values, **kwargs)
File "/usr/local/lib/python2.6/dist-packages/django/db/models/query.py" in insert_query
1436. return query.get_compiler(using=using).execute_sql(return_id)
File "/usr/local/lib/python2.6/dist-packages/django/db/models/sql/compiler.py" in execute_sql
791. cursor = super(SQLInsertCompiler, self).execute_sql(None)
File "/usr/local/lib/python2.6/dist-packages/django/db/models/sql/compiler.py" in execute_sql
735. cursor.execute(sql, params)
File "/usr/local/lib/python2.6/dist-packages/django/db/backends/util.py" in execute
34. return self.cursor.execute(sql, params)
File "/usr/local/lib/python2.6/dist-packages/django/db/backends/mysql/base.py" in execute
86. return self.cursor.execute(query, args)
File "/usr/lib/pymodules/python2.6/MySQLdb/cursors.py" in execute
166. self.errorhandler(self, exc, value)
File "/usr/lib/pymodules/python2.6/MySQLdb/connections.py" in defaulterrorhandler
35. raise errorclass, errorvalue
Exception Type: IntegrityError at /app/address/add/
Exception Value: (1048, "Column 'createdBy_id' cannot be null"
You specifically defined logic to add the request to the form, but are not using it.
You need to instantiate your form with request as a keyword argument (according to your code).
form = addForm(request.POST, request=request)
save(request) does nothing with the request.
Most people prefer just accepting a new argument in save().
def save(self, request):
obj = super(AddressForm, self).save(commit=False)
obj.createdBy = request.user
obj.save()
Finally, there's no point in the if block in the save method if createdBy is not nullable: it's required. There's no "if" about it.
You excluded the createdBy field in your form and according to your Model definition, it's a required field since you're using a ModelForm.

django brookie - 'NoneType' object has no attribute 'status'

Bumped into the Brookie app the other day and had a look around. Very nice for creating invoice PDF's on save.
However, I get this error on creating a new invoice. I know that the error arises because there is no object and therefore it cannot have an attribute.
class InvoiceAdmin(admin.ModelAdmin):
list_display = ('client', 'status', 'date', total_monetized, is_expired, pdf_invoice)
list_filter = ('status', 'client')
exclude = ('invoice_no',)
ordering = ('id', )
search_fields = ['client__company', ]
readonly_fields = ()
inlines = [ItemInline,]
class Media:
js = ('http://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.min.js', 'brookie/js/brookie.js')
def get_readonly_fields(self, request, obj=None):
readonly = super(InvoiceAdmin, self).get_readonly_fields(request, obj)
# if the invoice is send you can no longer alter it
if obj.status in br_settings.INVOICE_FINISH_STATUS:
readonly = ('invoice_id', 'client', 'date', 'currency', 'tax', 'hourly_rate')
return readonly
def save_model(self, request, obj, form, change):
obj.save()
if obj.status in br_settings.INVOICE_FINISH_STATUS:
# Set the invoice id
if not obj.invoice_no:
invoice_list = Invoice.objects.filter(invoice_no__isnull=False).order_by('-invoice_no')
try:
invoice = invoice_list[0]
except:
# There are no numbered invoices
invoice_no = getattr(br_settings, 'INVOICE_START_NUMBER', 1)
else:
invoice_no = invoice.invoice_no + 1
obj.invoice_no = invoice_no
obj.save()
# Generate the pdf for this invoice
context_dict = {'invoice': obj,
'client': obj.client,
'items': obj.items.all(),}
generate_pdf(obj.invoice_id, context_dict, "brookie/invoice_%s_pdf.html" % obj.currency, save=True)
I've tried various ways to test the existance of the object before readonly fields. Readonly fields are returned if the invoice is in a state othehr than indevelopment (1)
How can I ignore the get_readonly_fields function if the object is new?
I've also tried adding a default=1 to the model, testing for the existance of an obj before trying to access the status attibute to no sucess.
Thanks in advance
## EDIT ##
Traceback:
File "C:\Python26\lib\site-packages\django\core\handlers\base.py" in get_response
100. response = callback(request, *callback_args, **callback_kwargs)
File "C:\Python26\lib\site-packages\django\contrib\admin\options.py" in wrapper
239. return self.admin_site.admin_view(view)(*args, **kwargs)
File "C:\Python26\lib\site-packages\django\utils\decorators.py" in _wrapped_view
76. response = view_func(request, *args, **kwargs)
File "C:\Python26\lib\site-packages\django\views\decorators\cache.py" in _wrapped_view_func
69. response = view_func(request, *args, **kwargs)
File "C:\Python26\lib\site-packages\django\contrib\admin\sites.py" in inner
190. return view(request, *args, **kwargs)
File "C:\Python26\lib\site-packages\django\utils\decorators.py" in _wrapper
21. return decorator(bound_func)(*args, **kwargs)
File "C:\Python26\lib\site-packages\django\utils\decorators.py" in _wrapped_view
76. response = view_func(request, *args, **kwargs)
File "C:\Python26\lib\site-packages\django\utils\decorators.py" in bound_func
17. return func(self, *args2, **kwargs2)
File "C:\Python26\lib\site-packages\django\db\transaction.py" in _commit_on_success
299. res = func(*args, **kw)
File "C:\Python26\lib\site-packages\django\contrib\admin\options.py" in add_view
773. ModelForm = self.get_form(request)
File "C:\Python26\lib\site-packages\django\contrib\admin\options.py" in get_form
356. exclude.extend(self.get_readonly_fields(request, obj))
File "C:\Sites\media\bread-and-pepper-django-brookie-a1a8102\brookie\admin.py" in get_readonly_fields
124. if obj.status in br_settings.INVOICE_FINISH_STATUS:
Exception Type: AttributeError at /admin/brookie/invoice/add/
Exception Value: 'NoneType' object has no attribute 'status'
The problem is that you're trying to evaluate the status attribute even in the case where obj is None, which is apparently an acceptable case.
To fix it, you could change this:
# if the invoice is send you can no longer alter it
if obj.status in br_settings.INVOICE_FINISH_STATUS:
readonly = ('invoice_id', 'client', 'date', 'currency', 'tax', 'hourly_rate')
To this:
# if the invoice is send you can no longer alter it
if obj and obj.status in br_settings.INVOICE_FINISH_STATUS:
readonly = ('invoice_id', 'client', 'date', 'currency', 'tax', 'hourly_rate')

Django & markdown issue: 'NoneType' object has no attribute 'strip'

I have a problem about markdown and django integration. After I installed markdown and change my model class to:
class Page(models.Model):
class Translation(multilingual.Translation):
title = models.CharField(verbose_name="Title", max_length=30,
help_text="Put a title (max. 30 chars) of your page -e.g. About, CEO, Contact etc...")
content_markdown = models.TextField(verbose_name="Markdown Content",
help_text="Use Markdown syntax here.")
content = models.TextField(verbose_name="Page content as HTML",
blank=True, null=True,
help_text="You don't have to touch here.")
slug = models.SlugField(verbose_name="Slug",
help_text="Put here the name of your page without space -e.g. research-development")
class Meta:
verbose_name_plural = "Pages"
ordering = ['id']
def __unicode__(self):
return self.title
def save(self):
import markdown
self.content = markdown.markdown(self.content_markdown)
super(Page, self).save()
def get_absolute_url(self):
# return "/%s/%s" % (self.menu.slug, self.slug)
return "/%s" % self.slug
I got these traceback:
Traceback:
File "/usr/local/lib/python2.6/dist-packages/django/core/handlers/base.py" in get_response
92. response = callback(request, *callback_args, **callback_kwargs)
File "/usr/local/lib/python2.6/dist-packages/django/contrib/admin/options.py" in wrapper
226. return self.admin_site.admin_view(view)(*args, **kwargs)
File "/usr/local/lib/python2.6/dist-packages/django/views/decorators/cache.py" in _wrapped_view_func
44. response = view_func(request, *args, **kwargs)
File "/usr/local/lib/python2.6/dist-packages/django/contrib/admin/sites.py" in inner
186. return view(request, *args, **kwargs)
File "/usr/local/lib/python2.6/dist-packages/django/db/transaction.py" in _commit_on_success
240. res = func(*args, **kw)
File "/usr/local/lib/python2.6/dist-packages/django/contrib/admin/options.py" in add_view
734. self.save_model(request, new_object, form, change=False)
File "/usr/local/lib/python2.6/dist-packages/django/contrib/admin/options.py" in save_model
557. obj.save()
File "/media/DATA/Programming/pyworkspace/djangoprojects/youngjin/../youngjin/archive/models.py" in save
37. self.content = markdown.markdown(self.content_markdown)
File "/usr/local/lib/python2.6/dist-packages/Markdown-2.0.1-py2.6.egg/markdown/__init__.py" in markdown
587. return md.convert(text)
File "/usr/local/lib/python2.6/dist-packages/Markdown-2.0.1-py2.6.egg/markdown/__init__.py" in convert
370. if not source.strip():
Exception Type: AttributeError at /admin/archive/page/add/
Exception Value: 'NoneType' object has no attribute 'strip'
I cannot really figure out what is the problem. Any idea?
I would guess that self.content_markdown might be null in some cases (but I am not sure why, because the default should be to return empty strings instead of null-values if not specified otherwise).
Anyway, can you try the following, just to be sure?
self.content = markdown.markdown(self.content_markdown or u'')