├── .gitignore
├── LICENSE
├── README.md
├── cbv_tutorial
├── __init__.py
├── fixtures
│ └── users.json
├── settings.py
├── urls.py
└── wsgi.py
├── core
├── __init__.py
├── admin.py
├── apps.py
├── migrations
│ └── __init__.py
├── mixins.py
├── models.py
├── templatetags
│ ├── __init__.py
│ └── custom_tags.py
├── tests.py
├── urls.py
└── views.py
├── djangocbv
├── __init__.py
├── admin.py
├── apps.py
├── filters.py
├── forms.py
├── migrations
│ ├── 0001_initial.py
│ └── __init__.py
├── mixins.py
├── models.py
├── templates
│ ├── _categories_partial.html
│ ├── django_cbv_home.html
│ └── djangocbv
│ │ ├── _article_content_partial.html
│ │ ├── article_detail.html
│ │ ├── article_form.html
│ │ ├── article_list.html
│ │ ├── category_detail.html
│ │ ├── category_form.html
│ │ ├── category_list.html
│ │ ├── document_detail.html
│ │ ├── document_form.html
│ │ └── document_list.html
├── tests.py
├── urls.py
└── views.py
├── manage.py
├── requirements.txt
├── rsp.bat
├── run.bat
└── templates
├── _partial_account.html
├── _partial_messages.html
├── about.html
├── help.html
├── home.html
├── registration
├── logged_out.html
└── login.html
└── site_base.html
/.gitignore:
--------------------------------------------------------------------------------
1 | ### Django ###
2 | *.log
3 | *.pot
4 | *.pyc
5 | __pycache__/
6 | local_settings.py
7 | *.pyc
8 | *.swp
9 | *.swo
10 | *.bz
11 | *.b
12 | .hg
13 | .ropeproject
14 | coverage_html_report
15 | *.db
16 | .idea
17 | .vagrant
18 | local.py
19 | db.sqlite3
20 | media
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
33 |
".join(
40 | ['{1}'.format(reverse(p.name), p.name) for p in self.get_urlpatterns()]
41 | )
42 | return ctx
--------------------------------------------------------------------------------
/core/models.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models
5 |
6 |
7 |
--------------------------------------------------------------------------------
/core/templatetags/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spapas/cbv-tutorial/ae6f8a1011617bb4db0fdb648a6d0ec64f73f950/core/templatetags/__init__.py
--------------------------------------------------------------------------------
/core/templatetags/custom_tags.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | from django import template
3 |
4 | register = template.Library()
5 |
6 |
7 |
--------------------------------------------------------------------------------
/core/tests.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.test import TestCase
5 |
6 | # Create your tests here.
7 |
--------------------------------------------------------------------------------
/core/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls import include, url
2 | from django.contrib import admin
3 |
4 | from . import views
5 |
6 | urlpatterns = [
7 | url(r'^$', views.HomeCustomClassView.as_view(), name='home-ccv'),
8 | url(r'^ccv-empty/$', views.CustomClassView.as_view(), name='ccv-empty'),
9 | url(r'^ccv-with-values/$', views.CustomClassView.as_view(header='Hello', context=['hello', 'world', ], footer='Bye', ), name='ccv-with-values'),
10 | url(r'^ccv-inherits/$', views.InheritsCustomClassView.as_view(), name='ccv-inherits'),
11 | url(r'^default-header-bccv/$', views.DefaultHeaderBetterCustomClassView.as_view(), name='default-header-bccv'),
12 | url(r'^json-ccv/$', views.JsonCustomClassView.as_view(), name='json-ccv'),
13 | url(r'^default-header-json-ccv/$', views.DefaultHeaderJsonCustomClassView.as_view(), name='default-header-json-ccv'),
14 | url(r'^json-default-header-ccv/$', views.JsonDefaultHeaderCustomClassView.as_view(), name='json-default-header-ccv'),
15 | url(r'^default-header-context-ccv/$', views.DefaultHeaderContextCustomClassView.as_view(), name='default-header-context-ccv'),
16 | url(r'^default-header-mixin-bccv/$', views.DefaultHeaderMixinBetterCustomClassView.as_view(), name='default-header-mixin-bccv'),
17 | url(r'^default-context-mixin-bccv/$', views.DefaultContextMixinBetterCustomClassView.as_view(), name='default-context-mixin-bccv'),
18 | url(r'^default-header-context-mixin-bccv/$', views.DefaultHeaderContextMixinBetterCustomClassView.as_view(), name='default-header-context-mixin-bccv'),
19 | url(r'^default-header-mixin-json-ccv/$', views.JsonDefaultHeaderMixinCustomClassView.as_view(), name='default-header-mixin-json-ccv'),
20 | url(r'^header-prefix-bccv/$', views.HeaderPrefixBetterCustomClassView.as_view(), name='header-prefix-bccv'),
21 | url(r'^header-prefix-default-bccv/$', views.HeaderPrefixDefaultBetterCustomClassView.as_view(), name='header-prefix-default-bccv'),
22 | url(r'extra-context-12-bccv/$', views.ExtraContext12BetterCustomClassView.as_view(), name='extra-context-12-bccv'),
23 | url(r'extra-context-21-bccv/$', views.ExtraContext21BetterCustomClassView.as_view(), name='extra-context-21-bccv'),
24 | url(r'all-together-now-bccv/$', views.AllTogetherNowBetterCustomClassView.as_view(), name='all-together-now-bccv'),
25 | ]
26 |
--------------------------------------------------------------------------------
/core/views.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.shortcuts import render
5 | from django.http import HttpResponse
6 | import json
7 |
8 | from . import mixins
9 |
10 | class CustomClassView:
11 | context = []
12 | header = ''
13 |
14 | def __init__(self, **kwargs):
15 | self.kwargs = kwargs
16 | for (k,v) in kwargs.items():
17 | setattr(self, k, v)
18 |
19 | def render(self):
20 | print ("Custom Class View render")
21 | return """
22 |
23 |
24 |
25 |
26 |
27 |
28 | {header}
29 | {body}
30 |
31 |
32 | """.format(
33 | header=self.header, body='
'.join(self.context),
34 | )
35 |
36 | @classmethod
37 | def as_view(cls, *args, **kwargs):
38 | def view(request, ):
39 | instance = cls(**kwargs)
40 | return HttpResponse(instance.render())
41 |
42 | return view
43 |
44 | class InheritsCustomClassView(CustomClassView, ):
45 | header = "Hi"
46 | context = ['test', 'test2' ]
47 |
48 |
49 | class BetterCustomClassView(CustomClassView, ):
50 | def get_header(self, ):
51 | print ("Better Custom Class View get_header")
52 | return self.header if self.header else ""
53 |
54 | def get_context(self , ):
55 | return self.context if self.context else []
56 |
57 | def render_context(self):
58 | context = self.get_context()
59 | if context:
60 | return '
'.join(context)
61 | return ""
62 |
63 | def render(self):
64 | print ("Better Custom Class View render")
65 | return """
66 |
67 |
68 |
69 |
70 |
71 |
72 | {header}
73 | {body}
74 |
75 |
76 | """.format(
77 | header=self.get_header(), body=self.render_context(),
78 | )
79 |
80 |
81 | class DefaultHeaderBetterCustomClassView(BetterCustomClassView, ):
82 | def get_header(self, ):
83 | return self.header if self.header else "DEFAULT HEADER"
84 |
85 | class DefaultContextBetterCustomClassView(BetterCustomClassView, ):
86 | def get_context(self, ):
87 | return self.context if self.context else ["DEFAULT CONTEXT"]
88 |
89 | class JsonCustomClassView:
90 | header = ''
91 | context = []
92 |
93 | def get_header(self, ):
94 | return self.header if self.header else ""
95 |
96 | def get_context(self, ):
97 | return self.context if self.context else []
98 |
99 | @classmethod
100 | def as_view(cls, *args, **kwargs):
101 | def view(request, ):
102 | instance = cls(**kwargs)
103 | return HttpResponse(json.dumps({
104 | 'header': instance.get_header(),
105 | 'context': instance.get_context(),
106 | }))
107 |
108 | return view
109 |
110 |
111 | class DefaultHeaderJsonCustomClassView(DefaultHeaderBetterCustomClassView, JsonCustomClassView):
112 | pass
113 | print (DefaultHeaderJsonCustomClassView.__mro__)
114 | class JsonDefaultHeaderCustomClassView(JsonCustomClassView, DefaultHeaderBetterCustomClassView):
115 | pass
116 | print (JsonDefaultHeaderCustomClassView.__mro__)
117 | class DefaultHeaderContextCustomClassView(DefaultHeaderBetterCustomClassView, DefaultContextBetterCustomClassView):
118 | pass
119 | print (DefaultHeaderContextCustomClassView.__mro__)
120 |
121 | class DefaultHeaderMixinBetterCustomClassView(mixins.DefaultHeaderMixin, BetterCustomClassView):
122 | pass
123 |
124 | class DefaultContextMixinBetterCustomClassView(mixins.DefaultContextMixin, BetterCustomClassView):
125 | pass
126 |
127 | class DefaultHeaderContextMixinBetterCustomClassView(mixins.DefaultHeaderMixin, mixins.DefaultContextMixin, BetterCustomClassView):
128 | pass
129 |
130 |
131 | class JsonDefaultHeaderMixinCustomClassView(mixins.DefaultHeaderMixin, JsonCustomClassView):
132 | pass
133 |
134 | class HeaderPrefixBetterCustomClassView(mixins.HeaderPrefixMixin, BetterCustomClassView):
135 | header='Hello!'
136 |
137 | class HeaderPrefixDefaultBetterCustomClassView(mixins.HeaderPrefixMixin, mixins.DefaultHeaderSuperMixin, BetterCustomClassView):
138 | pass
139 |
140 | class ExtraContext12BetterCustomClassView(mixins.ExtraContext1Mixin, mixins.ExtraContext2Mixin, BetterCustomClassView):
141 | pass
142 |
143 | class ExtraContext21BetterCustomClassView(mixins.ExtraContext2Mixin, mixins.ExtraContext1Mixin, BetterCustomClassView):
144 | pass
145 |
146 | class AllTogetherNowBetterCustomClassView(
147 | mixins.HeaderPrefixMixin,
148 | mixins.DefaultHeaderSuperMixin,
149 | mixins.ExtraContext1Mixin,
150 | mixins.ExtraContext2Mixin,
151 | BetterCustomClassView
152 | ):
153 | pass
154 |
155 |
156 | class HomeCustomClassView(mixins.UrlPatternsMixin, BetterCustomClassView, ):
157 | def get_urlpatterns(self):
158 | from core.urls import urlpatterns
159 | return urlpatterns
160 |
161 | def render_context(self):
162 | return self.render_patterns()
163 |
--------------------------------------------------------------------------------
/djangocbv/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spapas/cbv-tutorial/ae6f8a1011617bb4db0fdb648a6d0ec64f73f950/djangocbv/__init__.py
--------------------------------------------------------------------------------
/djangocbv/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 |
3 | from .models import Category
4 |
5 | class CategoryAdmin(admin.ModelAdmin):
6 | pass
7 | admin.site.register(Category, CategoryAdmin)
8 |
--------------------------------------------------------------------------------
/djangocbv/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class DjangocbvConfig(AppConfig):
5 | name = 'djangocbv'
6 |
--------------------------------------------------------------------------------
/djangocbv/filters.py:
--------------------------------------------------------------------------------
1 | import django_filters
2 | from .models import Article, Document
3 |
4 | class ArticleFilter(django_filters.FilterSet):
5 | class Meta:
6 | model = Article
7 | fields = {
8 | 'title': ['icontains']
9 | }
10 |
11 |
12 | class DocumentFilter(django_filters.FilterSet):
13 | class Meta:
14 | model = Document
15 | fields = {
16 | 'description': ['icontains']
17 | }
--------------------------------------------------------------------------------
/djangocbv/forms.py:
--------------------------------------------------------------------------------
1 | from django.forms import ModelForm
2 | from .models import Article, Document, Category
3 |
4 |
5 | class ArticleForm(ModelForm):
6 | class Meta:
7 | model = Article
8 | fields = ['category', 'owned_by', 'title', 'content', ]
9 |
10 | def __init__(self, *args, **kwargs):
11 | self.request = kwargs.pop('request', None)
12 | super().__init__(*args, **kwargs)
13 |
14 | if not self.request.user.has_perm('djangocbv.admin_access'):
15 | self.fields.pop('owned_by')
16 |
17 |
18 | class DocumentForm(ModelForm):
19 | class Meta:
20 | model = Document
21 | fields = ['category', 'description', 'file', 'owned_by', ]
22 |
23 | def __init__(self, *args, **kwargs):
24 | self.request = kwargs.pop('request', None)
25 | super().__init__(*args, **kwargs)
26 |
27 | if not self.request.user.has_perm('djangocbv.admin_access'):
28 | self.fields.pop('owned_by')
--------------------------------------------------------------------------------
/djangocbv/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.0.3 on 2018-03-12 22:58
2 |
3 | from django.conf import settings
4 | from django.db import migrations, models
5 | import django.db.models.deletion
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | initial = True
11 |
12 | dependencies = [
13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL),
14 | ]
15 |
16 | operations = [
17 | migrations.CreateModel(
18 | name='Article',
19 | fields=[
20 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
21 | ('status', models.CharField(choices=[('DRAFT', 'Draft'), ('PUBLISHED', 'Published'), ('REMOVED', 'Removed')], max_length=16)),
22 | ('created_on', models.DateTimeField(auto_now_add=True)),
23 | ('modified_on', models.DateTimeField(auto_now=True)),
24 | ('published_on', models.DateTimeField(blank=True, null=True)),
25 | ('title', models.CharField(max_length=128)),
26 | ('content', models.TextField()),
27 | ],
28 | options={
29 | 'permissions': (('publisher_access', 'Publisher Access'), ('admin_access', 'Admin Access')),
30 | 'abstract': False,
31 | },
32 | ),
33 | migrations.CreateModel(
34 | name='Category',
35 | fields=[
36 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
37 | ('name', models.CharField(max_length=128)),
38 | ],
39 | ),
40 | migrations.CreateModel(
41 | name='Document',
42 | fields=[
43 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
44 | ('status', models.CharField(choices=[('DRAFT', 'Draft'), ('PUBLISHED', 'Published'), ('REMOVED', 'Removed')], max_length=16)),
45 | ('created_on', models.DateTimeField(auto_now_add=True)),
46 | ('modified_on', models.DateTimeField(auto_now=True)),
47 | ('published_on', models.DateTimeField(blank=True, null=True)),
48 | ('description', models.CharField(max_length=128)),
49 | ('file', models.FileField(upload_to='')),
50 | ('category', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='djangocbv.Category')),
51 | ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='document_created_by', to=settings.AUTH_USER_MODEL)),
52 | ('modified_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='document_modified_by', to=settings.AUTH_USER_MODEL)),
53 | ('owned_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='document_owned_by', to=settings.AUTH_USER_MODEL)),
54 | ],
55 | options={
56 | 'permissions': (('publisher_access', 'Publisher Access'), ('admin_access', 'Admin Access')),
57 | 'abstract': False,
58 | },
59 | ),
60 | migrations.AddField(
61 | model_name='article',
62 | name='category',
63 | field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='djangocbv.Category'),
64 | ),
65 | migrations.AddField(
66 | model_name='article',
67 | name='created_by',
68 | field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='article_created_by', to=settings.AUTH_USER_MODEL),
69 | ),
70 | migrations.AddField(
71 | model_name='article',
72 | name='modified_by',
73 | field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='article_modified_by', to=settings.AUTH_USER_MODEL),
74 | ),
75 | migrations.AddField(
76 | model_name='article',
77 | name='owned_by',
78 | field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='article_owned_by', to=settings.AUTH_USER_MODEL),
79 | ),
80 | ]
81 |
--------------------------------------------------------------------------------
/djangocbv/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spapas/cbv-tutorial/ae6f8a1011617bb4db0fdb648a6d0ec64f73f950/djangocbv/migrations/__init__.py
--------------------------------------------------------------------------------
/djangocbv/mixins.py:
--------------------------------------------------------------------------------
1 | from django.contrib import messages
2 | from django.contrib.auth.mixins import UserPassesTestMixin
3 | from django.http import HttpResponse
4 | from django.urls import reverse
5 | from django.contrib.auth.mixins import LoginRequiredMixin
6 | import csv, json
7 |
8 | from .models import Category
9 |
10 |
11 | class AuditableMixin:
12 | def form_valid(self, form, ):
13 | if not form.instance.created_by_id:
14 | form.instance.created_by = self.request.user
15 | form.instance.modified_by = self.request.user
16 | return super().form_valid(form)
17 |
18 |
19 | class LimitAccessMixin:
20 | def get_queryset(self):
21 | qs = super().get_queryset()
22 | if self.request.user.has_perm('djangocbv.admin_access') or self.request.user.has_perm('djangocbv.publisher_access') :
23 | return qs
24 | return qs.filter(owned_by=self.request.user)
25 |
26 |
27 | class HideRemovedMixin:
28 | def get_queryset(self):
29 | qs = super().get_queryset()
30 | if self.request.user.has_perm('djangocbv.admin_access') or self.request.user.has_perm('djangocbv.publisher_access') :
31 | return qs
32 | return qs.exclude(status='REMOVED')
33 |
34 |
35 | class ModerationMixin:
36 | def form_valid(self, form):
37 | if form.instance.status != 'REMOVED':
38 | if self.request.user.has_perm('djangocbv.publisher_access'):
39 | form.instance.status = 'PUBLISHED'
40 | else:
41 | form.instance.status = 'DRAFT'
42 |
43 | return super().form_valid(form)
44 |
45 |
46 | class SetInitialMixin(object,):
47 | def get_initial(self):
48 | initial = super(SetInitialMixin, self).get_initial()
49 | initial.update(self.request.GET.dict())
50 | return initial
51 |
52 |
53 | class SuccessMessageMixin(object, ):
54 | success_message = ''
55 |
56 | def get_success_message(self):
57 | return self.success_message
58 |
59 | def form_valid(self, form):
60 | messages.success(self.request, self.get_success_message())
61 | return super().form_valid(form)
62 |
63 |
64 | class AnyPermissionRequiredMixin(UserPassesTestMixin):
65 | permissions = []
66 |
67 | def test_func(self):
68 | for p in self.permissions:
69 | if self.request.user.has_perm(p):
70 | return True
71 | return False
72 |
73 |
74 | class AdminOrPublisherPermissionRequiredMixin(AnyPermissionRequiredMixin):
75 | permissions = ['djangocbv.admin_access', 'djangocbv.publisher_access']
76 |
77 |
78 | class RequestArgMixin:
79 | def get_form_kwargs(self):
80 | kwargs = super(RequestArgMixin, self).get_form_kwargs()
81 | kwargs.update({'request': self.request})
82 | return kwargs
83 |
84 |
85 | class CategoriesContextMixin:
86 | def get_context_data(self, **kwargs):
87 | ctx = super().get_context_data(**kwargs)
88 | ctx['categories'] = Category.objects.all()
89 | return ctx
90 |
91 |
92 | class AddFilterMixin:
93 | filter_class = None
94 |
95 | def get_context_data(self, **kwargs):
96 | ctx = super().get_context_data(**kwargs)
97 | if not self.filter_class:
98 | raise NotImplementedError("Please define filter_class when using AddFilterMixin")
99 | filter = self.filter_class(self.request.GET, queryset=self.get_queryset())
100 | ctx['filter'] = filter
101 | ctx[self.context_object_name] = filter.qs
102 | return ctx
103 |
104 |
105 | class ExportCsvMixin:
106 | def render_to_response(self, context, **response_kwargs):
107 | if self.request.GET.get('csv'):
108 | response = HttpResponse(content_type='text/csv')
109 | response['Content-Disposition'] = 'attachment; filename="export.csv"'
110 |
111 | writer = csv.writer(response)
112 | for idx, o in enumerate(context['object_list']):
113 | if idx == 0: # Write headers
114 | writer.writerow(k for (k,v) in o.__dict__.items() if not k.startswith('_'))
115 | writer.writerow(v for (k,v) in o.__dict__.items() if not k.startswith('_'))
116 |
117 | return response
118 | return super().render_to_response(context, **response_kwargs)
119 |
120 |
121 | class JsonDetailMixin:
122 | def render_to_response(self, context, **response_kwargs):
123 | if self.request.GET.get('json'):
124 | response = HttpResponse(content_type='application/json')
125 | response.write(json.dumps(dict( (k,str(v)) for k,v in self.object.__dict__.items() )))
126 | return response
127 | return super().render_to_response(context, **response_kwargs)
128 |
129 |
130 | class SetOwnerIfNeeded:
131 | def form_valid(self, form, ):
132 | if not form.instance.owned_by_id:
133 | form.instance.owned_by = self.request.user
134 | return super().form_valid(form)
135 |
136 |
137 | class ChangeStatusMixin:
138 | new_status = None
139 |
140 | def form_valid(self, form, ):
141 | if not self.new_status:
142 | raise NotImplementedError("Please define new_status when using ChangeStatusMixin")
143 | form.instance.status = self.new_status
144 | return super().form_valid(form)
145 |
146 |
147 | class ContentCreateMixin(SuccessMessageMixin,
148 | AuditableMixin,
149 | SetOwnerIfNeeded,
150 | RequestArgMixin,
151 | SetInitialMixin,
152 | ModerationMixin,
153 | LoginRequiredMixin):
154 | success_message = 'Object successfully created!'
155 |
156 |
157 | class ContentUpdateMixin(SuccessMessageMixin,
158 | AuditableMixin,
159 | SetOwnerIfNeeded,
160 | RequestArgMixin,
161 | SetInitialMixin,
162 | ModerationMixin,
163 | LimitAccessMixin,
164 | LoginRequiredMixin):
165 | success_message = 'Object successfully updated!'
166 |
167 |
168 | class ContentListMixin(ExportCsvMixin, AddFilterMixin, HideRemovedMixin, ):
169 | pass
170 |
171 |
172 | class ContentRemoveMixin(SuccessMessageMixin,
173 | AdminOrPublisherPermissionRequiredMixin,
174 | AuditableMixin,
175 | ChangeStatusMixin,):
176 | http_method_names = ['post',]
177 | new_status = 'REMOVED'
178 | fields = []
179 | success_message = 'Object successfully removed!'
180 |
181 |
182 | class ContentUnpublishMixin(SuccessMessageMixin,
183 | AdminOrPublisherPermissionRequiredMixin,
184 | AuditableMixin,
185 | ChangeStatusMixin,):
186 | http_method_names = ['post',]
187 | new_status = 'DRAFT'
188 | fields = []
189 | success_message = 'Object successfully unpublished!'
190 |
--------------------------------------------------------------------------------
/djangocbv/models.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 | from django.db import models
3 |
4 |
5 | STATUS_CHOICES = (
6 | ('DRAFT', 'Draft', ),
7 | ('PUBLISHED', 'Published', ),
8 | ('REMOVED', 'Removed', ),
9 | )
10 |
11 |
12 | class Category(models.Model):
13 | name = models.CharField(max_length=128, )
14 |
15 | def __str__(self):
16 | return self.name
17 |
18 | class Meta:
19 | permissions = (
20 | ("publisher_access", "Publisher Access"),
21 | ("admin_access", "Admin Access"),
22 | )
23 |
24 |
25 | class AbstractGeneralInfo(models.Model):
26 | status = models.CharField(max_length=16, choices=STATUS_CHOICES, )
27 | category = models.ForeignKey('category', on_delete=models.PROTECT, )
28 | created_on = models.DateTimeField(auto_now_add=True, )
29 | created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name='%(class)s_created_by', )
30 | modified_on = models.DateTimeField(auto_now=True, )
31 | modified_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name='%(class)s_modified_by', )
32 |
33 | owned_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name='%(class)s_owned_by', )
34 | published_on = models.DateTimeField(blank=True, null=True)
35 |
36 | class Meta:
37 | abstract = True
38 |
39 |
40 | class Article(AbstractGeneralInfo):
41 | title = models.CharField(max_length=128, )
42 | content = models.TextField()
43 |
44 |
45 | class Document(AbstractGeneralInfo):
46 | description = models.CharField(max_length=128, )
47 | file = models.FileField()
48 |
--------------------------------------------------------------------------------
/djangocbv/templates/_categories_partial.html:
--------------------------------------------------------------------------------
1 | List of categories: {% for cat in categories %}{{ cat }}{% if not forloop.last %}|{% endif %}{% endfor %}
--------------------------------------------------------------------------------
/djangocbv/templates/django_cbv_home.html:
--------------------------------------------------------------------------------
1 | {% extends "site_base.html" %}
2 |
3 | {% block title%}Home{% endblock %}
4 | {% block content %}
5 | Welcome !
6 |
7 | You can visit:
8 |
9 |
10 | {{ urls |safe}}
11 | {% for u in urls %}
12 |
13 | {% endfor %}
14 |
15 |
16 | {% include "_categories_partial.html" %}
17 |
18 | {% endblock %}
--------------------------------------------------------------------------------
/djangocbv/templates/djangocbv/_article_content_partial.html:
--------------------------------------------------------------------------------
1 | {{ article.content|linebreaks }}
--------------------------------------------------------------------------------
/djangocbv/templates/djangocbv/article_detail.html:
--------------------------------------------------------------------------------
1 | {% extends "site_base.html" %}
2 |
3 | {% block title%}Article detail{% endblock %}
4 | {% block content %}
5 |
6 | {{ article.title }}
7 |
8 | {{ article.status }}
9 |
10 | {{ article.owned_by }} {% if published %} at {{ article.published_on}} {% endif %}
11 |
12 | {% include "djangocbv/_article_content_partial.html" %}
13 |
14 |
15 |
16 |
17 | {% endblock %}
--------------------------------------------------------------------------------
/djangocbv/templates/djangocbv/article_form.html:
--------------------------------------------------------------------------------
1 | {% extends "site_base.html" %}
2 |
3 | {% block title%}Article form{% endblock %}
4 | {% block content %}
5 |
6 |
11 | {% endblock %}
--------------------------------------------------------------------------------
/djangocbv/templates/djangocbv/article_list.html:
--------------------------------------------------------------------------------
1 | {% extends "site_base.html" %}
2 |
3 | {% block title%}Article list{% endblock %}
4 | {% block extra_style%}
5 |
29 | {% endblock %}
30 | {% block content %}
31 |
32 |
Id | 44 |Control | 45 |Title | 46 |Status | 47 |Content | 48 |Category | 49 |Owner | 50 |
---|---|---|---|---|---|---|
{{ article.id }} | 57 |58 | View 59 | Edit 60 | {% if article.status == 'DRAFT' %} 61 | 62 | {% endif %} 63 | {% if article.status != 'DRAFT' %} 64 | 65 | {% endif %} 66 | | 67 |{{ article.title }} | 68 |{{ article.get_status_display }} | 69 |70 | {{ article.content|truncatewords:20 }} 71 | 72 | | 73 |{{ article.category }} | 74 |{{ article.owned_by }} | 75 |
6 | Export csv 7 |
8 | 9 | 10 | 11 |Edit | 15 |Title | 16 |# articles | 17 |# documents | 18 |Add | 19 |
---|---|---|---|---|
{{ cat.id }} | 26 |{{ cat.name }} | 27 |{{ cat.article_cnt }} | 28 |{{ cat.document_cnt }} | 29 |30 | Article 31 | | 32 | Document 33 | | 34 |
12 | Download 13 |
14 | 15 | 16 | 17 | 18 | {% endblock %} -------------------------------------------------------------------------------- /djangocbv/templates/djangocbv/document_form.html: -------------------------------------------------------------------------------- 1 | {% extends "site_base.html" %} 2 | 3 | {% block title%}Document form{% endblock %} 4 | {% block content %} 5 | 6 | 12 | {% endblock %} -------------------------------------------------------------------------------- /djangocbv/templates/djangocbv/document_list.html: -------------------------------------------------------------------------------- 1 | {% extends "site_base.html" %} 2 | 3 | {% block title%}Document list{% endblock %} 4 | {% block extra_style%} 5 | 27 | {% endblock %} 28 | {% block content %} 29 | 30 |31 |
35 | Export csv 36 | 37 | 38 |Id | 42 |Control | 43 |Title | 44 |Status | 45 |Category | 46 |Owner | 47 |File | 48 |
---|---|---|---|---|---|---|
{{ doc.id }} | 55 |56 | View 57 | Edit 58 | {% if doc.status == 'DRAFT' %} 59 | 60 | {% endif %} 61 | 62 | {% if doc.status != 'DRAFT' %} 63 | 64 | {% endif %} 65 | | 66 |{{ doc.description }} | 67 |{{ doc.get_status_display }} | 68 |{{ doc.category }} | 69 |{{ doc.owned_by }} | 70 |{{ doc.file }} | 71 |
Your username and password didn't match. Please try again.
9 | {% endif %} 10 | 11 | {% if next %} 12 | {% if user.is_authenticated %} 13 |Your account doesn't have access to this page. To proceed, 14 | please login with an account that has access.
15 | {% else %} 16 |Please login to see this page.
17 | {% endif %} 18 | {% endif %} 19 | 20 | 36 | {% endblock %} -------------------------------------------------------------------------------- /templates/site_base.html: -------------------------------------------------------------------------------- 1 | {% load custom_tags %} 2 | 3 | 4 | 5 | 6 | 36 | {% block extra_style %} 37 | {% endblock %} 38 | 39 | 40 |