├── .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 |
68 |
69 |
70 | 71 |
72 |
73 | DjangoSuit.com
74 | Demo data will reset every 15 minutes 75 |
76 |
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 | 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 | 9 | 12 | 13 | 14 | 15 | 18 | 19 |
Flag presented by 10 | http://www.geonames.org 11 |
Flag of {{ original.name }} 16 | 17 |
20 | {% else %} 21 | Item is not saved yet 22 | {% endif %} 23 | -------------------------------------------------------------------------------- /templates/admin/examples/country/tab_info.html: -------------------------------------------------------------------------------- 1 |

Documentation 2 |

3 | 4 | 5 | 6 | 13 | 14 | 15 | 16 | 27 | 28 |
Tabs 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 |
Includes 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 |
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 | --------------------------------------------------------------------------------