├── .gitignore
├── README.rst
├── __init__.py
├── admin.py
├── models.py
├── requirements.txt
├── static
└── css
│ └── examples.css
└── templates
└── admin
├── base_site.html
└── examples
├── city
└── change_list.html
├── continent
└── change_list.html
├── country
├── tab_disclaimer.html
├── tab_flag.html
└── tab_info.html
└── wysiwygeditor
└── change_form.html
/.gitignore:
--------------------------------------------------------------------------------
1 | *.py[co]
2 | *.egg*
3 | *.swp
4 | *~
5 | *.log
6 | .idea
7 | migrations
8 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | Examples
2 | ========
3 |
4 | This repository is **only for** `demo application `_ of `Django Suit `_ project.
5 |
6 | Main repository is here: https://github.com/darklow/django-suit
7 |
8 |
9 | Django Suit
10 | ===========
11 |
12 | Read more: http://djangosuit.com/
13 |
14 | Documentation: http://django-suit.readthedocs.org/
15 |
--------------------------------------------------------------------------------
/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/darklow/django-suit-examples/68c5628aa1269964f8e5cea290630f5457019764/__init__.py
--------------------------------------------------------------------------------
/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin, messages
2 | from django.contrib.admin import ModelAdmin, SimpleListFilter
3 | from django.contrib.admin.widgets import AdminDateWidget
4 | from django.contrib.auth.admin import UserAdmin
5 | from django.contrib.auth.forms import UserChangeForm
6 | from django.contrib.auth.models import User
7 | from django.forms import TextInput, ModelForm, Textarea, Select
8 | from reversion import VersionAdmin
9 | from import_export.admin import ImportExportModelAdmin
10 | from suit_ckeditor.widgets import CKEditorWidget
11 | from suit_redactor.widgets import RedactorWidget
12 | from .models import Country, Continent, KitchenSink, Category, City, \
13 | Microwave, Fridge, WysiwygEditor, ReversionedItem, ImportExportItem
14 | from suit.admin import SortableTabularInline, SortableModelAdmin, \
15 | SortableStackedInline
16 | from suit.widgets import SuitDateWidget, SuitSplitDateTimeWidget, \
17 | EnclosedInput, LinkedSelect, AutosizedTextarea
18 | from django_select2 import AutoModelSelect2Field, AutoHeavySelect2Widget
19 | from mptt.admin import MPTTModelAdmin
20 |
21 |
22 | # Inlines for KitchenSink
23 | class CountryInlineForm(ModelForm):
24 | class Meta:
25 | widgets = {
26 | 'code': TextInput(attrs={'class': 'input-mini'}),
27 | 'population': TextInput(attrs={'class': 'input-medium'}),
28 | 'independence_day': SuitDateWidget,
29 | }
30 |
31 |
32 | class CountryInline(SortableTabularInline):
33 | form = CountryInlineForm
34 | model = Country
35 | fields = ('name', 'code', 'population',)
36 | extra = 1
37 | verbose_name_plural = 'Countries (Sortable example)'
38 | sortable = 'order'
39 |
40 |
41 | class ContinentAdmin(SortableModelAdmin):
42 | search_fields = ('name',)
43 | list_display = ('name', 'countries')
44 | inlines = (CountryInline,)
45 | sortable = 'order'
46 |
47 | def countries(self, obj):
48 | return len(obj.country_set.all())
49 |
50 | def suit_row_attributes(self, obj):
51 | class_map = {
52 | 'Europe': 'success',
53 | 'South America': 'warning',
54 | 'North America': 'success',
55 | 'Africa': 'error',
56 | 'Australia': 'warning',
57 | 'Asia': 'info',
58 | 'Antarctica': 'info',
59 | }
60 |
61 | css_class = class_map.get(obj.name)
62 | if css_class:
63 | return {'class': css_class}
64 |
65 | def suit_cell_attributes(self, obj, column):
66 | if column == 'countries':
67 | return {'class': 'text-center'}
68 | elif column == 'right_aligned':
69 | return {'class': 'text-right muted'}
70 |
71 |
72 | admin.site.register(Continent, ContinentAdmin)
73 |
74 |
75 | class CityInlineForm(ModelForm):
76 | class Meta:
77 | widgets = {
78 | 'area': EnclosedInput(prepend='icon-globe', append='km2 ',
79 | attrs={'class': 'input-small'}),
80 | 'population': EnclosedInput(append='icon-user',
81 | attrs={'class': 'input-small'}),
82 | }
83 |
84 |
85 | class CityInline(admin.TabularInline):
86 | form = CityInlineForm
87 | model = City
88 | extra = 3
89 | verbose_name_plural = 'Cities'
90 | suit_classes = 'suit-tab suit-tab-cities'
91 |
92 |
93 | class CountryForm(ModelForm):
94 | class Meta:
95 | widgets = {
96 | 'code': TextInput(attrs={'class': 'input-mini'}),
97 | 'independence_day': SuitDateWidget,
98 | 'area': EnclosedInput(prepend='icon-globe', append='km2 ',
99 | attrs={'class': 'input-small'}),
100 | 'population': EnclosedInput(prepend='icon-user',
101 | append=' ',
105 | attrs={'class': 'input-small'}),
106 | 'description': AutosizedTextarea,
107 | 'architecture': AutosizedTextarea(attrs={'class': 'span5'}),
108 | }
109 |
110 |
111 | class CountryAdmin(ModelAdmin):
112 | form = CountryForm
113 | search_fields = ('name', 'code')
114 | list_display = ('name', 'code', 'continent', 'independence_day')
115 | list_filter = ('continent',)
116 | date_hierarchy = 'independence_day'
117 | list_select_related = True
118 |
119 | inlines = (CityInline,)
120 |
121 | fieldsets = [
122 | (None, {
123 | 'classes': ('suit-tab suit-tab-general',),
124 | 'fields': ['name', 'continent', 'code', 'independence_day']
125 | }),
126 | ('Statistics', {
127 | 'classes': ('suit-tab suit-tab-general',),
128 | 'description': 'EnclosedInput widget examples',
129 | 'fields': ['area', 'population']}),
130 | ('Autosized textarea', {
131 | 'classes': ('suit-tab suit-tab-general',),
132 | 'description': 'AutosizedTextarea widget example - adapts height '
133 | 'based on user input',
134 | 'fields': ['description']}),
135 | ('Architecture', {
136 | 'classes': ('suit-tab suit-tab-cities',),
137 | 'description': 'Tabs can contain any fieldsets and inlines',
138 | 'fields': ['architecture']}),
139 | ]
140 |
141 | suit_form_tabs = (('general', 'General'), ('cities', 'Cities'),
142 | ('flag', 'Flag'), ('info', 'Info on tabs'))
143 |
144 | suit_form_includes = (
145 | ('admin/examples/country/tab_disclaimer.html', 'middle', 'cities'),
146 | ('admin/examples/country/tab_flag.html', '', 'flag'),
147 | ('admin/examples/country/tab_info.html', '', 'info'),
148 | )
149 |
150 |
151 | admin.site.register(Country, CountryAdmin)
152 |
153 |
154 | class CountryFilter(SimpleListFilter):
155 | """
156 | List filter example that shows only referenced(used) values
157 | """
158 | title = 'country'
159 | parameter_name = 'country'
160 |
161 | def lookups(self, request, model_admin):
162 | # You can use also "Country" instead of "model_admin.model"
163 | # if this is not direct relation
164 | countries = set([c.country for c in model_admin.model.objects.all()])
165 | return [(c.id, c.name) for c in countries]
166 |
167 | def queryset(self, request, queryset):
168 | if self.value():
169 | return queryset.filter(country__id__exact=self.value())
170 | else:
171 | return queryset
172 |
173 |
174 | class KitchenSinkForm(ModelForm):
175 | class Meta:
176 | widgets = {
177 | 'multiple2': TextInput(attrs={'class': 'input-small'}),
178 | 'date': AdminDateWidget(attrs={'class': 'vDateField input-small'}),
179 | 'date_widget': SuitDateWidget,
180 | 'datetime_widget': SuitSplitDateTimeWidget,
181 | 'textfield': AutosizedTextarea(attrs={'rows': '2'}),
182 | 'linked_foreign_key': LinkedSelect,
183 |
184 | 'enclosed1': EnclosedInput(append='icon-plane',
185 | attrs={'class': 'input-medium'}),
186 | 'enclosed2': EnclosedInput(prepend='icon-envelope',
187 | append=' ',
189 | attrs={'class': 'input-medium'}),
190 | }
191 |
192 |
193 | # Inlines for KitchenSink
194 | class FridgeInlineForm(ModelForm):
195 | class Meta:
196 | model = Fridge
197 | widgets = {
198 | 'description': AutosizedTextarea(
199 | attrs={'class': 'input-medium', 'rows': 2,
200 | 'style': 'width:95%'}),
201 | 'type': Select(attrs={'class': 'input-small'}),
202 | }
203 |
204 |
205 | class FridgeInline(SortableTabularInline):
206 | model = Fridge
207 | form = FridgeInlineForm
208 | extra = 1
209 | verbose_name_plural = 'Fridges (Tabular inline)'
210 |
211 |
212 | class MicrowaveInline(SortableStackedInline):
213 | model = Microwave
214 | extra = 1
215 | verbose_name_plural = 'Microwaves (Stacked inline)'
216 |
217 |
218 | # Kitchen sink model admin
219 | class KitchenSinkAdmin(admin.ModelAdmin):
220 | raw_id_fields = ()
221 | form = KitchenSinkForm
222 | inlines = (FridgeInline, MicrowaveInline)
223 | search_fields = ['name']
224 | radio_fields = {"horizontal_choices": admin.HORIZONTAL,
225 | 'vertical_choices': admin.VERTICAL}
226 | list_editable = ('boolean', )
227 | list_filter = ('choices', 'date', CountryFilter)
228 | readonly_fields = ('readonly_field',)
229 | raw_id_fields = ('raw_id_field',)
230 | fieldsets = [
231 | (None, {'fields': ['name', 'help_text', 'textfield',
232 | ('multiple_in_row', 'multiple2'),
233 | 'file', 'readonly_field']}),
234 | ('Date and time', {
235 | 'description': 'Improved date/time widgets (SuitDateWidget, '
236 | 'SuitSplitDateTimeWidget) . Uses original JS.',
237 | 'fields': ['date_widget', 'datetime_widget']}),
238 |
239 | ('Foreign key relations',
240 | {'description': 'Original select and linked select feature',
241 | 'fields': ['country', 'linked_foreign_key', 'raw_id_field']}),
242 |
243 | ('EnclosedInput widget',
244 | {
245 | 'description': 'Supports Twitter Bootstrap prepended, '
246 | 'appended inputs',
247 | 'fields': ['enclosed1', 'enclosed2']}),
248 |
249 | ('Boolean and choices',
250 | {'fields': ['boolean', 'boolean_with_help', 'choices',
251 | 'horizontal_choices', 'vertical_choices']}),
252 |
253 | ('Collapsed settings', {
254 | 'classes': ('collapse',),
255 | 'fields': ['hidden_checkbox', 'hidden_choice']}),
256 | ('And one more collapsable', {
257 | 'classes': ('collapse',),
258 | 'fields': ['hidden_charfield', 'hidden_charfield2']}),
259 |
260 | ]
261 | list_display = (
262 | 'name', 'help_text', 'choices', 'horizontal_choices', 'boolean')
263 |
264 | def get_formsets(self, request, obj=None):
265 | """
266 | Set extra=0 for inlines if object already exists
267 | """
268 | for inline in self.get_inline_instances(request):
269 | formset = inline.get_formset(request, obj)
270 | if obj:
271 | formset.extra = 0
272 | yield formset
273 |
274 |
275 | admin.site.register(KitchenSink, KitchenSinkAdmin)
276 |
277 | #
278 | # Extend original user admin class
279 | # Limit user change list queryset
280 | # Add suit date widgets and special warning for user save
281 | #
282 | class SuitUserChangeForm(UserChangeForm):
283 | class Meta:
284 | model = User
285 | widgets = {
286 | 'last_login': SuitSplitDateTimeWidget,
287 | 'date_joined': SuitSplitDateTimeWidget,
288 | }
289 |
290 |
291 | class SuitAdminUser(UserAdmin):
292 | form = SuitUserChangeForm
293 |
294 | def queryset(self, request):
295 | qs = super(SuitAdminUser, self).queryset(request)
296 | return qs.filter(id=6) if request.user.username == 'demo' else qs
297 |
298 | def response_change(self, request, obj):
299 | messages.warning(request, 'User data change is prevented in demo mode')
300 | return super(SuitAdminUser, self).response_change(request, obj)
301 |
302 |
303 | admin.site.unregister(User)
304 | admin.site.register(User, SuitAdminUser)
305 |
306 |
307 |
308 | ##################################
309 | #
310 | # Integrations examples
311 | #
312 | ##################################
313 |
314 | #
315 | # Django-mptt
316 | # https://github.com/django-mptt/django-mptt/
317 | #
318 | class CategoryAdmin(MPTTModelAdmin, SortableModelAdmin):
319 | """
320 | Example of django-mptt and sortable together. Important note:
321 | If used together MPTTModelAdmin must be before SortableModelAdmin
322 | """
323 | mptt_level_indent = 20
324 | search_fields = ('name', 'slug')
325 | prepopulated_fields = {'slug': ('name',)}
326 | list_display = ('name', 'slug', 'is_active')
327 | list_editable = ('is_active',)
328 | list_display_links = ('name',)
329 | sortable = 'order'
330 |
331 |
332 | admin.site.register(Category, CategoryAdmin)
333 |
334 |
335 | #
336 | # Django-select2
337 | # https://github.com/applegrew/django-select2
338 | #
339 | class CountryChoices(AutoModelSelect2Field):
340 | queryset = Country.objects
341 | search_fields = ['name__icontains', ]
342 |
343 |
344 | class CityForm(ModelForm):
345 | country_verbose_name = Country._meta.verbose_name
346 | country = CountryChoices(
347 | label=country_verbose_name.capitalize(),
348 | widget=AutoHeavySelect2Widget(
349 | select2_options={
350 | 'width': '220px',
351 | 'placeholder': 'Lookup %s ...' % country_verbose_name
352 | }
353 | )
354 | )
355 |
356 | class Meta:
357 | model = City
358 | widgets = {
359 | 'area': EnclosedInput(prepend='icon-globe', append='km2 ',
360 | attrs={'class': 'input-small'}),
361 | 'population': EnclosedInput(prepend='icon-user',
362 | append=' ',
366 | attrs={'class': 'input-small'}),
367 | }
368 |
369 |
370 | class CityAdmin(ModelAdmin):
371 | form = CityForm
372 | search_fields = ('name', 'country__name')
373 | list_display = ('name', 'country', 'capital', 'continent')
374 | list_filter = (CountryFilter, 'capital')
375 | fieldsets = [
376 | (None, {'fields': ['name', 'country', 'capital']}),
377 | ('Statistics', {
378 | 'description': 'EnclosedInput widget examples',
379 | 'fields': ['area', 'population']}),
380 | ]
381 |
382 | def continent(self, obj):
383 | return obj.country.continent
384 |
385 |
386 | admin.site.register(City, CityAdmin)
387 |
388 | #
389 | # Wysiwyg editor integration examples
390 | #
391 | class WysiwygEditorForm(ModelForm):
392 | class Meta:
393 | model = WysiwygEditor
394 |
395 | _ck_editor_toolbar = [
396 | {'name': 'basicstyles', 'groups': ['basicstyles', 'cleanup']},
397 | {'name': 'paragraph',
398 | 'groups': ['list', 'indent', 'blocks', 'align']},
399 | {'name': 'document', 'groups': ['mode']}, '/',
400 | {'name': 'styles'}, {'name': 'colors'},
401 | {'name': 'insert_custom',
402 | 'items': ['Image', 'Flash', 'Table', 'HorizontalRule']},
403 | {'name': 'about'}]
404 |
405 | _ck_editor_config = {'autoGrow_onStartup': True,
406 | 'autoGrow_minHeight': 100,
407 | 'autoGrow_maxHeight': 250,
408 | 'extraPlugins': 'autogrow',
409 | 'toolbarGroups': _ck_editor_toolbar}
410 | widgets = {
411 | 'redactor': RedactorWidget(editor_options={
412 | 'buttons': ['html', '|', 'formatting', '|', 'bold', 'italic']}),
413 | 'redactor2': RedactorWidget,
414 | 'ckeditor': CKEditorWidget(editor_options=_ck_editor_config),
415 | }
416 |
417 |
418 | class WysiwygEditorAdmin(ModelAdmin):
419 | form = WysiwygEditorForm
420 | search_fields = ('name',)
421 | list_display = ('name',)
422 | fieldsets = [
423 | (None, {'fields': ['name', 'redactor']}),
424 |
425 | ('Redactor', {
426 | 'classes': ('full-width',),
427 | 'description': 'Full width example',
428 | 'fields': ['redactor2']}),
429 |
430 | ('CK Editor', {
431 | 'classes': ('full-width',),
432 | 'description': 'CKEditor 4.x custom toolbar configuration example',
433 | 'fields': ['ckeditor']})
434 | ]
435 |
436 |
437 | admin.site.register(WysiwygEditor, WysiwygEditorAdmin)
438 |
439 |
440 | class ReversionedItemAdmin(VersionAdmin):
441 | search_fields = ('name',)
442 | list_display = ('name', 'quality', 'is_active')
443 |
444 |
445 | admin.site.register(ReversionedItem, ReversionedItemAdmin)
446 |
447 |
448 | class ImportExportDemoAdmin(ImportExportModelAdmin):
449 | search_fields = ('name',)
450 | list_display = ('name', 'quality', 'is_active')
451 |
452 |
453 | admin.site.register(ImportExportItem, ImportExportDemoAdmin)
454 |
--------------------------------------------------------------------------------
/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 | from mptt.fields import TreeForeignKey
3 | from mptt.models import MPTTModel
4 |
5 |
6 | class Continent(models.Model):
7 | name = models.CharField(max_length=256)
8 | order = models.PositiveIntegerField()
9 |
10 | def __unicode__(self):
11 | return self.name
12 |
13 | class Meta:
14 | ordering = ['name']
15 |
16 | def save(self, force_insert=False, force_update=False, using=None):
17 | return super(Continent, self).save(force_insert, force_update, using)
18 |
19 |
20 | class Country(models.Model):
21 | name = models.CharField(max_length=256)
22 | code = models.CharField(max_length=2,
23 | help_text='ISO 3166-1 alpha-2 - two character '
24 | 'country code')
25 | independence_day = models.DateField(blank=True, null=True)
26 | continent = models.ForeignKey(Continent, null=True)
27 | area = models.BigIntegerField(blank=True, null=True)
28 | population = models.BigIntegerField(blank=True, null=True)
29 | order = models.PositiveIntegerField(default=0)
30 | description = models.TextField(blank=True,
31 | help_text='Try and enter few some more '
32 | 'lines')
33 | architecture = models.TextField(blank=True)
34 |
35 | def __unicode__(self):
36 | return self.name
37 |
38 | class Meta:
39 | ordering = ['name']
40 | verbose_name_plural = "Countries"
41 |
42 |
43 | TYPE_CHOICES = ((1, 'Awesome'), (2, 'Good'), (3, 'Normal'), (4, 'Bad'))
44 | TYPE_CHOICES2 = ((1, 'Hot'), (2, 'Normal'), (3, 'Cold'))
45 | TYPE_CHOICES3 = ((1, 'Tall'), (2, 'Normal'), (3, 'Short'))
46 |
47 |
48 | class KitchenSink(models.Model):
49 | name = models.CharField(max_length=64)
50 | help_text = models.CharField(max_length=64,
51 | help_text="Enter fully qualified name")
52 | multiple_in_row = models.CharField(max_length=64,
53 | help_text='Help text for multiple')
54 | multiple2 = models.CharField(max_length=10, blank=True)
55 | textfield = models.TextField(blank=True,
56 | verbose_name='Autosized textarea',
57 | help_text='Try and enter few some more lines')
58 |
59 | file = models.FileField(upload_to='.', blank=True)
60 | readonly_field = models.CharField(max_length=127, default='Some value here')
61 |
62 | date = models.DateField(blank=True, null=True)
63 | date_and_time = models.DateTimeField(blank=True, null=True)
64 |
65 | date_widget = models.DateField(blank=True, null=True)
66 | datetime_widget = models.DateTimeField(blank=True, null=True)
67 |
68 | boolean = models.BooleanField(default=True)
69 | boolean_with_help = models.BooleanField(
70 | help_text="Boolean field with help text")
71 |
72 | horizontal_choices = models.SmallIntegerField(choices=TYPE_CHOICES,
73 | default=1,
74 | help_text='Horizontal '
75 | 'choices look '
76 | 'like this')
77 | vertical_choices = models.SmallIntegerField(choices=TYPE_CHOICES2,
78 | default=2,
79 | help_text="Some help on "
80 | "vertical choices")
81 | choices = models.SmallIntegerField(choices=TYPE_CHOICES3,
82 | default=3,
83 | help_text="Help text")
84 | hidden_checkbox = models.BooleanField()
85 | hidden_choice = models.SmallIntegerField(choices=TYPE_CHOICES3,
86 | default=2, blank=True)
87 | hidden_charfield = models.CharField(max_length=64, blank=True)
88 | hidden_charfield2 = models.CharField(max_length=64, blank=True)
89 |
90 | country = models.ForeignKey(Country, related_name='foreign_key_country')
91 | linked_foreign_key = models.ForeignKey(Country, limit_choices_to={
92 | 'continent__name': 'Europe'}, related_name='foreign_key_linked')
93 | raw_id_field = models.ForeignKey(Country,
94 | help_text='Regular raw ID field',
95 | null=True, blank=True)
96 |
97 | enclosed1 = models.CharField(max_length=64, blank=True)
98 | enclosed2 = models.CharField(max_length=64, blank=True)
99 |
100 | def __unicode__(self):
101 | return self.name
102 |
103 | # Inline model for KitchenSink
104 | class Fridge(models.Model):
105 | kitchensink = models.ForeignKey(KitchenSink)
106 | name = models.CharField(max_length=64)
107 | type = models.SmallIntegerField(choices=TYPE_CHOICES3)
108 | description = models.TextField(blank=True)
109 | is_quiet = models.BooleanField()
110 | order = models.PositiveIntegerField()
111 |
112 | class Meta:
113 | ordering = ('order',)
114 |
115 | def __unicode__(self):
116 | return self.name
117 |
118 |
119 | # Inline model for KitchenSink
120 | class Microwave(models.Model):
121 | kitchensink = models.ForeignKey(KitchenSink)
122 | name = models.CharField(max_length=64)
123 | type = models.SmallIntegerField(choices=TYPE_CHOICES3, default=2,
124 | help_text='Choose wisely')
125 | is_compact = models.BooleanField()
126 | order = models.PositiveIntegerField()
127 |
128 | class Meta:
129 | ordering = ('order',)
130 |
131 | def __unicode__(self):
132 | return self.name
133 |
134 |
135 | ##################################
136 | #
137 | # Integrations examples
138 | #
139 | ##################################
140 |
141 | #
142 | # Django-mptt
143 | # https://github.com/django-mptt/django-mptt/
144 | #
145 | class Category(MPTTModel):
146 | name = models.CharField(max_length=64)
147 | slug = models.CharField(max_length=64)
148 | parent = TreeForeignKey('self', null=True, blank=True,
149 | related_name='children')
150 | is_active = models.BooleanField()
151 | order = models.IntegerField()
152 |
153 | def __unicode__(self):
154 | return self.name
155 |
156 | class Meta:
157 | verbose_name_plural = "Categories (django-mptt)"
158 |
159 | class MPTTMeta:
160 | order_insertion_by = ['order']
161 |
162 | def save(self, *args, **kwargs):
163 | super(Category, self).save(*args, **kwargs)
164 | Category.objects.rebuild()
165 |
166 |
167 | #
168 | # Django-select2
169 | # https://github.com/applegrew/django-select2
170 | #
171 | class City(models.Model):
172 | name = models.CharField(max_length=64)
173 | country = models.ForeignKey(Country)
174 | capital = models.BooleanField()
175 | area = models.BigIntegerField(blank=True, null=True)
176 | population = models.BigIntegerField(blank=True, null=True)
177 |
178 | def __unicode__(self):
179 | return self.name
180 |
181 | class Meta:
182 | verbose_name_plural = "Cities (django-select2)"
183 | unique_together = ('name', 'country')
184 |
185 |
186 | class WysiwygEditor(models.Model):
187 | name = models.CharField(max_length=64)
188 | redactor = models.TextField(verbose_name='Redactor small', blank=True)
189 | redactor2 = models.TextField(verbose_name='Redactor2', blank=True)
190 | ckeditor = models.TextField(verbose_name='CKEditor', blank=True)
191 |
192 | def __unicode__(self):
193 | return self.name
194 |
195 |
196 | class ReversionedItem(models.Model):
197 | name = models.CharField(max_length=64)
198 | quality = models.SmallIntegerField(choices=TYPE_CHOICES, default=1)
199 | is_active = models.BooleanField(default=False)
200 |
201 | def __unicode__(self):
202 | return self.name
203 |
204 | class ImportExportItem(models.Model):
205 | name = models.CharField(max_length=64)
206 | quality = models.SmallIntegerField(choices=TYPE_CHOICES, default=1)
207 | is_active = models.BooleanField(default=False)
208 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | django-mptt==0.6.0
2 | Django-Select2==4.0.0
3 | django-reversion
4 | django-suit-ckeditor
5 | django-suit-redactor
6 |
--------------------------------------------------------------------------------
/static/css/examples.css:
--------------------------------------------------------------------------------
1 | .countries-column .text, .countries-column a {
2 | text-align: center;
3 | }
4 |
--------------------------------------------------------------------------------
/templates/admin/base_site.html:
--------------------------------------------------------------------------------
1 | {% extends "admin/base.html" %}
2 | {% load admin_static %}
3 |
4 | {# {#Additional
content here #}
5 | {% block extrahead %}
6 | {{ block.super }}
7 |
8 |
10 |
27 |
28 | {% endblock %}
29 |
30 |
31 | {# Additional content here, some extra meta tags or favicon #}
32 | {#{% block extrahead %}#}
33 | {#{% endblock %}#}
34 |
35 |
36 | {# Additional CSS includes #}
37 | {% block extrastyle %}
38 |
39 | {% endblock %}
40 |
41 |
42 | {# Additional JS files in footer, right before #}
43 | {#{% block extrajs %}#}
44 | {# #}
45 | {#{% endblock %}#}
46 |
47 |
48 | {# Footer links (left side) #}
49 | {#{% block footer_links %}#}
50 | {# Documentation #}
51 | {#{% endblock %}#}
52 |
53 |
54 | {# Footer branding name (center) #}
55 | {#{% block footer_branding %}#}
56 | {#{% endblock %}#}
57 |
58 |
59 | {# Footer copyright (right side) #}
60 | {#{% block copyright %}#}
61 | {# Copyright © 2013 Client Developed by YourName #}
62 | {#{% endblock %}#}
63 |
64 |
65 | {% block header_content %}
66 | {{ block.super }}
67 |
77 | {% endblock %}
78 |
--------------------------------------------------------------------------------
/templates/admin/examples/city/change_list.html:
--------------------------------------------------------------------------------
1 | {# Extended just to add disclaimer #}
2 |
3 | {% extends "admin/change_list.html" %}
4 | {% load admin_list %}
5 |
6 | {% block search %}
7 | {{ block.super }}
8 |
9 | This is an example of
django-select2 package integration. Go to change form and you'll see AJAX Select2 widget for Country field.
10 |
11 | {% endblock %}
12 |
--------------------------------------------------------------------------------
/templates/admin/examples/continent/change_list.html:
--------------------------------------------------------------------------------
1 | {# Extended just to add disclaimer #}
2 |
3 | {% extends "admin/change_list.html" %}
4 | {% load admin_list %}
5 |
6 | {% block search %}
7 | {{ block.super }}
8 |
11 | {% endblock %}
12 |
--------------------------------------------------------------------------------
/templates/admin/examples/country/tab_disclaimer.html:
--------------------------------------------------------------------------------
1 |
2 | Custom include
3 |
4 |
5 | Read more on including templates in the last tab.
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/templates/admin/examples/country/tab_flag.html:
--------------------------------------------------------------------------------
1 | Country flag
2 | Content of this tab is also included template file
3 |
4 |
5 | {% if original.pk %}
6 |
7 |
8 | Flag presented by
9 |
10 | http://www.geonames.org
11 |
12 |
13 |
14 | Flag of {{ original.name }}
15 |
16 |
17 |
18 |
19 |
20 | {% else %}
21 | Item is not saved yet
22 | {% endif %}
23 |
--------------------------------------------------------------------------------
/templates/admin/examples/country/tab_info.html:
--------------------------------------------------------------------------------
1 | Documentation
2 |
3 |
4 |
5 | Tabs
6 |
7 | Tabs you see above are based on mostly CSS/JS solution, therefore integration of tabs is simple and non intrusive - all your form handling will work the same as before.
8 |
9 | Tabs can contain fieldsets, inlines and custom/included templates
10 |
11 | Form tabs documentation
12 |
13 |
14 |
15 | Includes
16 |
17 | Django Suit provides handy shortcut to include templates into forms, into several positions:
18 |
19 | top - above fieldsets
20 | middle - between fieldsets and inlines
21 | bottom - after inlines
22 |
23 | Suit includes are nothing but a shortcut. The same can be achieved by extending change_form.html and hooking into particular blocks. Suit includes can be used in combination with or without tabs.
24 |
25 | Form includes documentation
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/templates/admin/examples/wysiwygeditor/change_form.html:
--------------------------------------------------------------------------------
1 | {% extends 'admin/change_form.html' %}
2 | {% load url from future %}
3 | {% load i18n %}
4 |
5 |
6 | {% block after_related_objects %}
7 | {{ block.super }}
8 | Notes on WYSIWYG editors
9 |
10 | Examples you see above are
11 |
django-suit-redactor and
django-suit-ckeditor packages. We tested many existing apps, but many of them were missing tests, had implementation or other flaws, so we decided to create our own. We are planning to improve these packages in future (support file upload, etc.)
12 |
13 |
14 | Keep in mind
Imperavi Readactor is not part of Django Suit package and is licensed under Creative Commons Attribution-NonCommercial 3.0 license.
15 |
16 |
17 | Also you can integrate WYSIWYG editor using any other third party packages.
18 | More documentation on wysiwyg editors integration
is here
19 |
20 | Django Suit wysiwyg packages:
21 |
25 | Few third party packages we tested for Django Suit:
26 |
34 |
35 |
36 |
41 |
42 |
45 | {% endblock %}
46 |
--------------------------------------------------------------------------------