I'm trying to create a custom placeholder to add an admin-editable section on a website. Django-cms' documentation is really generic about what's to be done to add it.
The placeholder lives in a separate app, called contacts, already added to the settings.py file
Here's my code up to now (skipping imports to be less verbose):
models.py
class ContactsPlaceholder(models.Model):
phone = models.CharField(max_length=100)
email = models.CharField(max_length=100)
address = models.TextField(max_length=700)
# your fields
my_placeholder = PlaceholderField('contacts_contactsplaceholder')
# your methods
def __unicode__(self):
return "Contacts Placeholder"
views.py
def contacts(request):
# Load the the contacts from db
contactsfields = ContactsField.objects.all()
contacts_placeholder = ContactsPlaceholder.objects.all()
context = {"contactsfields" : contactsfields, "contacts_placeholder" : contacts_placeholder}
return render(request, "../templates/contacts.html", context)
def contacts_placeholder_detail(request, id): # is this really useful?
object = get_object_or_404(ContactsPlaceholder, id=id)
return render_to_response('../templates/contacts_placeholder_detail.html', {
'object': object,
}, context_instance=RequestContext(request))
contacts.html
{% extends "base.html" %}
{% load cms_tags %}
{% block content %}
<div>
<h1>Contacts</h1>
{% placeholder "contacts_placeholder" or %}
<p>Placeholder is empty</p>
{% endplaceholder %}
</div>
{% endblock content %}
and finally, admin.py
class ContactsPlaceholderAdmin(PlaceholderAdminMixin, admin.ModelAdmin):
# Editable fields
frontend_editable_fields = ("phone", "email", "address")
# Form fields order and sections
fieldsets = [
(None,{"fields" : ["phone", "email", "address"]})
]
admin.site.register(ContactsPlaceholder, ContactsPlaceholderAdmin)
With this configuration, after having run python manage.py syncdb I do get "Placeholder is empty" in the html response, and of course the placeholder is not editable using the standard frontend editor, which was the desired behaviour.
Going to 127.0.0.1:8000/admin/ i can see the "Contacts Placeholder" table, however when I click it or try to add items I do get the following error:
OperationalError at /us/admin/contacts/contactsplaceholder/
no such column: contacts_contactsplaceholder.phone
If the contacts() view is rendered via an app hook then using {% placeholder "contacts_placeholder" %} in the template won't work.
When using Placeholders in apps connected to the CMS via an AppHook you must use {% static_placeholder "MyPlaceholder" %} instead of the usual {% Placeholder "MyPlaceholder" %}
If you've got an app or a plugin in which you've created a PlaceholderField, then in your template which renders that, you'd need the following tag;
{% render_placeholder instance.placeholderfieldname %}
Related
I'm working on a Django site that displays a list of projects. You go to a page, you see a project, you click it, and you can then see project details and have the option to edit the page.
For each individual project, I use a template called project.html. Then on that page, I have a link to edit_project.html
Edit Project
Whenever I try to load the project page to view, I get this error:
Reverse for 'edit_project' with arguments '('',)' not found. 1
pattern(s) tried: ['edit_project/(?P\d+)/$']
I checked the urls, and they seemed fine. So, I tried hard-coding a project id that I knew was in the database. For example, I called:
Edit Project
instead of calling that same thing with "project.id". When I did that, the error went away.
Why does this work with a hard-coded value instead of a project.id variable?
I also tried deleting the database and all the migrations in case something strange was happening there. I got the same issue though.
Here is the code I used to set everything up in case it is helpful.
project.html
{% extends "profile/base.html" %}
{% block content %}
<p>
Edit Project
</p>
<p>Status: {{ status }}</p>
{% endblock content %}
edit_project.html
{% block content %}
<p>{{ project }}</p>
<p>Edit project:</p>
<form action="{% url 'profile:edit_project' project.id %}" method='post'>
{% csrf_token %}
{{ form.as_p }}
<button name="submit">Save Changes</button>
</form>
{% endblock content %}
views.py
#login_required
def edit_project(request, project_id):
"""Edit an existing project"""
project = Project.objects.get(id=project_id)
#Make sure the project belongs to the owner
if project.owner != request.user:
return Http404
if request.method != 'POST':
#Initial request; pre-fill form with the current entry
form = ProjectForm(instance=project)
else:
#POST data submitted; process data
form = ProjectForm(instance=project, data=request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('profile:project',args=[project.id]))
context = {'project':project, 'form':form}
return render(render, 'profile/edit_project.html', context)
(project) forms.py
class DateInput(forms.DateTimeInput):
input_type = 'date'
class ProjectForm(forms.ModelForm):
class Meta:
model = Project
fields = ['name','description','status', 'project_type', 'start_date','end_date', 'due_date']
labels = {'name':'Name', 'description':'Description:', 'status':'Status', 'project_type':'Project Type',
'start_date':'Start Date', 'end_date':'End Date', 'due_date': 'Due Date',}
widgets ={'description':forms.Textarea(attrs={'cols':80}),'start_date':DateInput(),
'end_date':DateInput(),'due_date':DateInput()}
urls.py
urlpatterns = [
#Home page
url('^$', views.index, name='index'),
#Show all projects
url('projects/$', views.projects, name='projects'),
#Details for single project
url('^projects/(?P<project_id>\d+)/$', views.project, name='project'),
#Page for editing a single project
url('^edit_project/(?P<project_id>\d+)/$', views.edit_project, name='edit_project'),
]
Is there any way to add form (for example feedback form) to every page in CMS? I really like to use Wagtail FormBuilder so Editor guy can change fields.
My first idea is to create custom form page (inherited from AbstractEmailForm) as site root child and load it to base.html trough template tag. I can access page properties this way but I cant render the form.
Here is my template tag:
#register.assignment_tag(takes_context=True)
def get_feedback_form(context):
return context['request'].site.root_page.get_children().type(FeedbackFormPage).first()
And this is how I use it from base.html:
{% get_feedback_form as feedback_form %}
...
{{ feedback_form.specific.title }} <-- this works
{{ feedback_form.specific.form.as_p }} <-- this doesnt work
It would be nice somehow to create a form as snippet or add it to Site Settings, but I didnt find how to do that.
The main issue is how you are generating the form in the template with .form.as_p.
You will need to generate the form with the .get_form function, but you are best to do it within your template as the current user and page needs to be past in as arguments like this.
form = feedback_form_page.get_form(
page=feedback_form_page, user=request.user)
You can see how the form is built for the AbstractForm model here:
https://github.com/wagtail/wagtail/blob/master/wagtail/wagtailforms/models.py#L278
Full detailed example below, along with how you could work the form selection into the Site Settings module.
Link to a Form in Site Settings
Assuming you are referring to the Site Settings contrib module:
http://docs.wagtail.io/en/v1.13/reference/contrib/settings.html
The 'Edit Handlers' section of the documentation explains a great way to link to a page inside of your site settings.
http://docs.wagtail.io/en/v1.13/reference/contrib/settings.html?highlight=site%20settings#edit-handlers
Example (in models.py):
from wagtail.contrib.settings.models import BaseSetting, register_setting
# ...
#register_setting
class MyCustomSettings(BaseSetting):
feedback_form_page = models.ForeignKey(
'wagtailcore.Page', null=True, on_delete=models.SET_NULL)
panels = [
# note the page type declared within the pagechooserpanel
PageChooserPanel('feedback_form_page', ['base.FormPage']),
]
Once you set this model up, you will need to do makemigration and migrate for the changes to work in admin. You will then see inside the settings menu a sub-menu titled 'My Custom Settings'
Adding linked Form to every page
Add a block (so it can be overridden in templates) that has an include in your base template (eg. myapp/templates/base.html).
<!-- Footer -->
<footer>
{% block feedback_form %}{% include "includes/feedback_form.html" %}{% endblock feedback_form %}
{% include "includes/footer.html" %}
</footer>
Create an include template (eg. myapp/templates/includes/feedback_form.html)
{% load feedback_form_tags wagtailcore_tags %}
{% get_feedback_form as feedback_form %}
<form action="{% pageurl feedback_form.page %}" method="POST" role="form">
<h3>{{ feedback_form.page.title}}</h3>
{% csrf_token %}
{{ feedback_form.form.as_p }}
<input type="submit">
</form>
Build a Template Tag to get the form and page
Your template tag needs to build the form with the page's self.get_form() function. Eg. you your template tag (base/templatetags/feedback_form)
from django import template
from myapp.models import MyCustomSettings
register = template.Library()
# https://docs.djangoproject.com/en/1.9/howto/custom-template-tags/
#register.assignment_tag(takes_context=True)
def get_feedback_form(context):
request = context['request']
my_custom_settings = MyCustomSettings.for_site(request.site)
feedback_form_page = my_custom_settings.feedback_form_page.specific
form = feedback_form_page.get_form(
page=feedback_form_page, user=request.user)
return {'page': feedback_form_page, 'form': form}
This still works in wagtail 2.3 just need to replace
#register.assignment_tag(takes_context=True)
with
#register.simple_tag(takes_context=True) to conform with django 2.2
Also {% load feedback_form_tags wagtailcore_tags %} assumes your file inside of templates tags is named feedback_form_tags.py. I also added an __init__.py in the template tags folder although I'm not sure that was actually necessary.
I want to pass data from models to template using CMS_plugins.py
I've made an app and standalone works. When i open link manualy
localhost:port/en/post_list.html all values are shown and works.
If i go on admin page and add plugin, values in mysql are stored but not presended in my template.html . I want to pass values in template.html
EDIT:
I manage to pass values to template. I edited cms_plugins.py
How can i hook data with "for" ?.
In code blow, nothing isnt shown in my template.html
<p>{{instance.firstline}}</p> ->>>this works
{ %block content %}
{{ instance.post.offer_option}}
<p>{{post.firstline}}</p>
<p>{{post.secline}}</p>
{% endfor %}
{% endblock}
if i change to :
{% for post in posts %}
<p>{{post.firstline}}</p>
<p>{{post.secline}}</p>
{% endfor %}
{% endblock}
In upper case if i run manualy url: localhost:port/en/pricing , I can see the result i want to be rendered in template.In template nothing isnt shown neither.
cms_plugins.py
class pricing(CMSPluginBase):
model = Post
name = _("pricing")
render_template = "post_list.html"
cache = False
def render(self, context, instance, placeholder):
context.update({'instance': instance})
return context
plugin_pool.register_plugin(pricing)
I want to loop data taken from database in rendered template. Using cms plugin.
I dont have problem looping data in html template. But if i use CMSPlugin to insert new app in placeholder, nothing shows.
If i run url localhost:port/test.html.I got input what i want. But rendered template doesnt loop data.
{% for post in posts %}
{{ post.firstoption }}
{% endfor %}
if I use code below, nothing shows in my rendered template. Values are passed in rendered template. Because, if i try {{instance.firstoption}} i get value shown in template. Problem is i cant loop data with tag instance.
{% for post in instance.posts %}
{{ post.firstoption }}
{% endfor %}
I also tried {% for post in instance.posts_set.all %}
and {% for post in instance.posts.all %}
cms_plugins.py
class pricing(CMSPluginBase):
model = mymodel
name = _("myplugin")
render_template = "template.html"
cache = False
def render(self, context, instance, placeholder):
context.update({'instance': instance})
return context
models.py
class mymodel(CMSPlugin):
firstoption = models.CharField(max_length=200)
def __str__(self):
return self.firstoption
It is probably because you need to call all on your posts
{% for post in instance.posts.all %}
{{ post.firstoption }}
{% endfor }
I want to add some html element (button, "a" tag, etc ) to a django admin page. How can i do it? Please help.
Not sure where you want to add your stuff but this is a solution I found somewhere else on SO to change the HTML of a FileField (in my case i wanted to display the current image in an ImageField).
In other words you can make a widget that modifies the html of the field you want to customize:
# Widget that modifies the output of a FileField
class OutputWidget(AdminFileWidget):
# Overloaded django magic
def render(self, name, value, attrs=None):
output = []
# This is the place where we edit the output
if value and getattr(value, "url", None):
image_url = value.url
output.append(u' <img src="%s" alt="%s" />' % (image_url, image_url, image_url))
output.append(super(AdminFileWidget, self).render(name, value, attrs))
return mark_safe(u''.join(output))
# ModelAdmin class that is applied to the model
class MyModelSettings(admin.ModelAdmin):
# Overloaded django magic
def formfield_for_dbfield(self, db_field, **kwargs):
# Look for the field we want to edit and register the widget with it
if db_field.name == 'nameOfFieldIWantToEdit':
request = kwargs.pop("request", None)
kwargs['widget'] = OutputWidget
return db_field.formfield(**kwargs)
return super(MyModelSettings,self).formfield_for_dbfield(db_field, **kwargs)
# Register my overloaded settings with the model
admin.site.register(MyModel, MyModelSettings)
The code goes into admin.py where you register your models.
From the docs:
The Django admin site
Customizing the Django admin interface
I used Omokoli's solution from above but to make the field use my custom widget I did:
class MyModelAdminForm(forms.ModelForm):
class Meta:
model = get_model('myapp', 'mymodel')
widgets = {
'original_link': OutputWidget,
}
You can create a file under templates/admin called base_site.html
(create the "admin" folder in your app).
add this code:
{% extends "admin/base_site.html" %}
{% load i18n %}
{% block userlinks %}
{% if site_url %}
{% trans 'View site' %} /
{% endif %}
{% if user.is_active and user.is_staff %}
{% url 'django-admindocs-docroot' as docsroot %}
{% if docsroot %}
{% trans 'Documentation' %} /
{% endif %}
{% endif %}
{% if user.has_usable_password %}
{% trans 'Change password' %} /
{% endif %}
{% trans 'Log out' %}
<!-- YOUR CUSTOM CODE HERE -->
<div class="your_custom_class">
{% trans 'your link 1' %} /
{% trans 'your link 2' %}
</div>
{% endblock %}
You can overwrite a lot of the admin html files. Make sure you extends the html you are overwriting, not to loose basic functionality.
See customize-django-admin-python for full admin templates tree you can overwrite.
You can also look at the base_site.html in Django project in Github. You'll find all of the "base_site" blocks you can plant your custom code in.