├── .gitignore ├── README.md ├── blog ├── __init__.py ├── admin.py ├── apps.py ├── context_processors.py ├── migrations │ ├── 0001_initial.py │ └── __init__.py ├── models.py ├── tests.py ├── urls.py └── views.py ├── contact ├── __init__.py ├── admin.py ├── apps.py ├── forms.py ├── migrations │ └── __init__.py ├── tests.py ├── urls.py └── views.py ├── core ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ └── __init__.py ├── models.py ├── templatetags │ ├── __init__.py │ └── core_extras.py ├── tests.py ├── urls.py └── views.py ├── manage.py ├── myproject ├── __init__.py ├── settings.py ├── urls.py └── wsgi.py ├── requirements.in ├── requirements.txt ├── sample_db_data.json ├── static ├── css │ └── main.css └── robots.txt └── templates ├── 404.html ├── base.html ├── blog ├── base.html ├── post_archive.html ├── post_detail.html ├── post_list.html └── post_list_by_tag.html ├── contact └── contact.html ├── core ├── home.html └── includes │ └── _pagination.html ├── flatpages └── default.html └── includes ├── _alerts.html ├── _field.html ├── _footer.html └── _header.html /.gitignore: -------------------------------------------------------------------------------- 1 | # python artifacts 2 | __pycache__/ 3 | *.pyc 4 | 5 | # django collected static files 6 | staticfiles/ 7 | media/ 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Django Blog Sample 2 | 3 | This repository contains a simple web application built with the Django web framework. 4 | 5 | The project has the next basic apps: 6 | - Blog (with draft/publish/archive management of posts, pagination and tag filtering) 7 | - Flatpages 8 | - Contact 9 | 10 | ## Setting Up a Development Environment 11 | 12 | 1. Create a Python 3.6 virtual environment. 13 | 2. Install required dependencies: 14 | ```bash 15 | pip install -r requirements.txt 16 | ``` 17 | 3. Create a MySQL database: 18 | ``` 19 | mysql -u root -p 20 | CREATE DATABASE djangoblogsample; 21 | \q 22 | ``` 23 | 4. Apply migrations to create database schema: 24 | ```bash 25 | python manage.py migrate 26 | ``` 27 | 5. Load sample data to the database: 28 | ```bash 29 | python manage.py loaddata sample_db_data.json 30 | ``` 31 | 6. Create a superuser that can log in to the admin panel: 32 | ```bash 33 | python manage.py createsuperuser 34 | ``` 35 | 7. Run the local server: 36 | ```bash 37 | python manage.py runserver 38 | ``` 39 | 40 | And, enjoy it! :tada: 41 | -------------------------------------------------------------------------------- /blog/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mickgeek/django-blog-sample/688dc000cdd6bf9c79a104a8c13e5fdb821164c9/blog/__init__.py -------------------------------------------------------------------------------- /blog/admin.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django import forms 4 | from django.utils import timezone 5 | from django.contrib import admin 6 | 7 | from ckeditor_uploader.widgets import CKEditorUploadingWidget 8 | 9 | from .models import Post, Tag 10 | 11 | 12 | class PostAdminForm(forms.ModelForm): 13 | class Meta: 14 | model = Post 15 | exclude = ('author', 'created_date', 'updated_date', 'published_date') 16 | widgets = { 17 | 'body': CKEditorUploadingWidget(), 18 | 'snippet': CKEditorUploadingWidget(config_name='small'), 19 | } 20 | 21 | 22 | @admin.register(Post) 23 | class PostAdmin(admin.ModelAdmin): 24 | list_display = ['title', 'created_date', 'published_date', 'author'] 25 | date_hierarchy = 'created_date' 26 | list_filter = ('created_date', 'status') 27 | search_fields = ['title'] 28 | 29 | form = PostAdminForm 30 | prepopulated_fields = {'slug': ('title',)} 31 | 32 | def save_model(self, request, obj, form, change): 33 | obj.author = request.user 34 | if (obj.published_date is None 35 | and obj.status in [Post.STATUS_PUBLISHED, Post.STATUS_ARCHIVED]): 36 | obj.published_date = timezone.now() 37 | if change: 38 | obj.updated_date = timezone.now() 39 | super(PostAdmin, self).save_model(request, obj, form, change) 40 | 41 | 42 | @admin.register(Tag) 43 | class TagAdmin(admin.ModelAdmin): 44 | prepopulated_fields = {'slug': ('name',)} 45 | -------------------------------------------------------------------------------- /blog/apps.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.apps import AppConfig 4 | 5 | 6 | class BlogConfig(AppConfig): 7 | name = 'blog' 8 | -------------------------------------------------------------------------------- /blog/context_processors.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.utils import timezone 4 | 5 | from .models import Tag, Post 6 | 7 | 8 | def post_tags(request): 9 | """ 10 | Returns a context variable that use for rendering the tag menu. 11 | """ 12 | statuses = [Post.STATUS_PUBLISHED, Post.STATUS_ARCHIVED] 13 | return { 14 | 'post_tags': Tag.objects.filter(post__status__in=statuses).distinct(), 15 | } 16 | -------------------------------------------------------------------------------- /blog/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.3 on 2017-07-09 04:33 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | import django.utils.timezone 9 | 10 | 11 | class Migration(migrations.Migration): 12 | 13 | initial = True 14 | 15 | dependencies = [ 16 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 17 | ] 18 | 19 | operations = [ 20 | migrations.CreateModel( 21 | name='Post', 22 | fields=[ 23 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 24 | ('title', models.CharField(max_length=150)), 25 | ('slug', models.SlugField(max_length=255, unique=True)), 26 | ('body', models.TextField()), 27 | ('snippet', models.TextField()), 28 | ('status', models.SmallIntegerField(choices=[(1, 'Draft'), (2, 'Published'), (3, 'Archived')])), 29 | ('created_date', models.DateTimeField(default=django.utils.timezone.now)), 30 | ('updated_date', models.DateTimeField(blank=True, null=True)), 31 | ('published_date', models.DateTimeField(blank=True, null=True)), 32 | ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 33 | ], 34 | options={ 35 | 'ordering': ('-created_date',), 36 | }, 37 | ), 38 | migrations.CreateModel( 39 | name='Tag', 40 | fields=[ 41 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 42 | ('name', models.CharField(max_length=50, unique=True)), 43 | ('slug', models.SlugField(max_length=100, unique=True)), 44 | ], 45 | options={ 46 | 'ordering': ('name',), 47 | }, 48 | ), 49 | migrations.AddField( 50 | model_name='post', 51 | name='tags', 52 | field=models.ManyToManyField(blank=True, to='blog.Tag'), 53 | ), 54 | ] 55 | -------------------------------------------------------------------------------- /blog/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mickgeek/django-blog-sample/688dc000cdd6bf9c79a104a8c13e5fdb821164c9/blog/migrations/__init__.py -------------------------------------------------------------------------------- /blog/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.db import models 4 | from django.template.defaultfilters import slugify 5 | from django.utils import timezone 6 | 7 | 8 | class Tag(models.Model): 9 | name = models.CharField(max_length=50, unique=True) 10 | slug = models.SlugField(max_length=100, unique=True) 11 | 12 | class Meta: 13 | ordering = ('name',) 14 | 15 | def __str__(self): 16 | return self.name 17 | 18 | 19 | class PostQuerySet(models.QuerySet): 20 | def active(self): 21 | return self.filter(status__in=[Post.STATUS_PUBLISHED, Post.STATUS_ARCHIVED]) 22 | 23 | def published(self): 24 | return self.filter(status=Post.STATUS_PUBLISHED) 25 | 26 | 27 | class Post(models.Model): 28 | STATUS_DRAFT = 1 29 | STATUS_PUBLISHED = 2 30 | STATUS_ARCHIVED = 3 31 | STATUSES = ( 32 | (STATUS_DRAFT, 'Draft'), 33 | (STATUS_PUBLISHED, 'Published'), 34 | (STATUS_ARCHIVED, 'Archived'), 35 | ) 36 | 37 | author = models.ForeignKey('auth.User', on_delete=models.CASCADE) 38 | title = models.CharField(max_length=150) 39 | slug = models.SlugField(max_length=255, unique=True) 40 | body = models.TextField() 41 | snippet = models.TextField() 42 | tags = models.ManyToManyField(Tag, blank=True) 43 | status = models.SmallIntegerField(choices=STATUSES) 44 | created_date = models.DateTimeField(default=timezone.now) 45 | updated_date = models.DateTimeField(blank=True, null=True) 46 | published_date = models.DateTimeField(blank=True, null=True) 47 | 48 | objects = PostQuerySet.as_manager() 49 | 50 | class Meta: 51 | ordering = ('-created_date',) 52 | 53 | def __str__(self): 54 | return self.title 55 | -------------------------------------------------------------------------------- /blog/tests.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.test import TestCase 4 | 5 | # Create your tests here. 6 | -------------------------------------------------------------------------------- /blog/urls.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.conf.urls import url 4 | 5 | from . import views 6 | from .views import PostListView, PostDetailView, PostListByTagView 7 | 8 | urlpatterns = [ 9 | url(r'^(page(?P\d+)/)?$', PostListView.as_view(), name='post-list'), 10 | url(r'^tags/(?P[-\w]+)/(page(?P\d+)/)?$', 11 | PostListByTagView.as_view(), name='post-list-by-tag'), 12 | url(r'^archive/$', views.post_archive, name='post-archive'), 13 | url(r'^(?P[-\w]+)/$', PostDetailView.as_view(), name='post-detail'), 14 | ] 15 | -------------------------------------------------------------------------------- /blog/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.views.generic.list import ListView 4 | from django.views.generic.detail import DetailView 5 | from django.shortcuts import render 6 | from django.utils import dateformat 7 | 8 | from .models import Post, Tag 9 | 10 | 11 | class PostListView(ListView): 12 | queryset = Post.objects.published() 13 | context_object_name = 'posts' 14 | 15 | 16 | class PostDetailView(DetailView): 17 | model = Post 18 | context_object_name = 'post' 19 | 20 | 21 | class PostListByTagView(ListView): 22 | context_object_name = 'posts' 23 | allow_empty = False 24 | template_name_suffix = '_list_by_tag' 25 | 26 | def get_queryset(self): 27 | return Post.objects.active().filter(tags__slug=self.kwargs['slug']) 28 | 29 | def get_context_data(self, **kwargs): 30 | context = super(PostListByTagView, self).get_context_data(**kwargs) 31 | context['tag'] = Tag.objects.get(slug=self.kwargs['slug']) 32 | return context 33 | 34 | 35 | def post_archive(request): 36 | posts = Post.objects.active() 37 | years = {} 38 | for post in posts: 39 | year = dateformat.format(post.published_date, 'Y') 40 | if year not in years: 41 | years[year] = [post] 42 | else: 43 | years[year].append(post) 44 | return render(request, 'blog/post_archive.html', {'years': years}) 45 | -------------------------------------------------------------------------------- /contact/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mickgeek/django-blog-sample/688dc000cdd6bf9c79a104a8c13e5fdb821164c9/contact/__init__.py -------------------------------------------------------------------------------- /contact/admin.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.contrib import admin 4 | 5 | # Register your models here. 6 | -------------------------------------------------------------------------------- /contact/apps.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.apps import AppConfig 4 | 5 | 6 | class ContactConfig(AppConfig): 7 | name = 'contact' 8 | -------------------------------------------------------------------------------- /contact/forms.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from smtplib import SMTPException 4 | 5 | from django import forms 6 | from django.core.mail import EmailMessage 7 | 8 | 9 | class ContactForm(forms.Form): 10 | name = forms.CharField( 11 | max_length=100, 12 | required=True, 13 | widget=forms.TextInput(attrs={'class': 'form-control'}), 14 | ) 15 | email = forms.EmailField( 16 | required=True, 17 | widget=forms.TextInput(attrs={'class': 'form-control'}), 18 | ) 19 | subject = forms.CharField( 20 | max_length=100, 21 | required=True, 22 | widget=forms.TextInput(attrs={'class': 'form-control'}), 23 | ) 24 | message = forms.CharField( 25 | required=True, 26 | widget=forms.Textarea(attrs={'class': 'form-control'}), 27 | ) 28 | 29 | def clean_subject(self): 30 | subject = ''.join(self.cleaned_data['subject'].splitlines()) 31 | return '[Contact form] ' + subject 32 | 33 | def send_email(self): 34 | email = EmailMessage( 35 | self.cleaned_data['subject'], 36 | self.cleaned_data['message'], 37 | '', 38 | ['admin@example.com'], 39 | reply_to=['{name} <{email}>'.format(**self.cleaned_data)], 40 | ) 41 | try: 42 | email.send() 43 | return True 44 | except SMTPException: 45 | return False 46 | -------------------------------------------------------------------------------- /contact/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mickgeek/django-blog-sample/688dc000cdd6bf9c79a104a8c13e5fdb821164c9/contact/migrations/__init__.py -------------------------------------------------------------------------------- /contact/tests.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.test import TestCase 4 | 5 | # Create your tests here. 6 | -------------------------------------------------------------------------------- /contact/urls.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.conf.urls import url 4 | 5 | from .views import ContactView 6 | 7 | urlpatterns = [ 8 | url(r'^$', ContactView.as_view(), name='contact'), 9 | ] 10 | -------------------------------------------------------------------------------- /contact/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.views.generic.edit import FormView 4 | from django.urls import reverse 5 | from django.contrib import messages 6 | 7 | from .forms import ContactForm 8 | 9 | 10 | class ContactView(FormView): 11 | form_class = ContactForm 12 | template_name = 'contact/contact.html' 13 | 14 | def get_success_url(self): 15 | return reverse('contact:contact') 16 | 17 | def form_valid(self, form): 18 | if form.send_email(): 19 | messages.info(self.request, 'Thank you for your message. We will be in touch shortly.') 20 | else: 21 | messages.error(self.request, "We couldn't send your message. Please try again later.") 22 | return super(ContactView, self).form_valid(form) 23 | -------------------------------------------------------------------------------- /core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mickgeek/django-blog-sample/688dc000cdd6bf9c79a104a8c13e5fdb821164c9/core/__init__.py -------------------------------------------------------------------------------- /core/admin.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.contrib import admin 4 | 5 | # Register your models here. 6 | -------------------------------------------------------------------------------- /core/apps.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.apps import AppConfig 4 | 5 | 6 | class CoreConfig(AppConfig): 7 | name = 'core' 8 | -------------------------------------------------------------------------------- /core/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mickgeek/django-blog-sample/688dc000cdd6bf9c79a104a8c13e5fdb821164c9/core/migrations/__init__.py -------------------------------------------------------------------------------- /core/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.db import models 4 | 5 | # Create your models here. 6 | -------------------------------------------------------------------------------- /core/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mickgeek/django-blog-sample/688dc000cdd6bf9c79a104a8c13e5fdb821164c9/core/templatetags/__init__.py -------------------------------------------------------------------------------- /core/templatetags/core_extras.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django import template 4 | 5 | register = template.Library() 6 | 7 | 8 | @register.filter 9 | def create_pagination_range(page, max_num_buttons): 10 | max_num_buttons = int(max_num_buttons) 11 | begin_page = max(1, page.number - int(max_num_buttons / 2)) 12 | end_page = begin_page + max_num_buttons 13 | if end_page > page.paginator.num_pages: 14 | end_page = page.paginator.num_pages + 1 15 | begin_page = max(1, end_page - max_num_buttons) 16 | return range(begin_page, end_page) 17 | -------------------------------------------------------------------------------- /core/tests.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.test import TestCase 4 | 5 | # Create your tests here. 6 | -------------------------------------------------------------------------------- /core/urls.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.conf.urls import url 4 | 5 | from . import views 6 | 7 | urlpatterns = [ 8 | url(r'^$', views.home, name='home'), 9 | ] 10 | -------------------------------------------------------------------------------- /core/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.shortcuts import render 4 | 5 | def home(request): 6 | return render(request, 'core/home.html') 7 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError: 10 | # The above import may fail for some other reason. Ensure that the 11 | # issue is really that Django is missing to avoid masking other 12 | # exceptions on Python 2. 13 | try: 14 | import django 15 | except ImportError: 16 | raise ImportError( 17 | "Couldn't import Django. Are you sure it's installed and " 18 | "available on your PYTHONPATH environment variable? Did you " 19 | "forget to activate a virtual environment?" 20 | ) 21 | raise 22 | execute_from_command_line(sys.argv) 23 | -------------------------------------------------------------------------------- /myproject/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mickgeek/django-blog-sample/688dc000cdd6bf9c79a104a8c13e5fdb821164c9/myproject/__init__.py -------------------------------------------------------------------------------- /myproject/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for myproject project. 3 | 4 | Generated by 'django-admin startproject' using Django 1.11.2. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.11/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/1.11/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = os.environ.get('SECRET_KEY') 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = ['djangoblogsample.local'] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | 'django.contrib.admin', 35 | 'django.contrib.auth', 36 | 'django.contrib.contenttypes', 37 | 'django.contrib.sessions', 38 | 'django.contrib.messages', 39 | 'django.contrib.staticfiles', 40 | 41 | 'django.contrib.sites', 42 | 'django.contrib.flatpages', 43 | 44 | 'ckeditor', 45 | 'ckeditor_uploader', 46 | 47 | 'core', 48 | 'contact', 49 | 'blog', 50 | ] 51 | 52 | MIDDLEWARE = [ 53 | 'django.middleware.security.SecurityMiddleware', 54 | 'django.contrib.sessions.middleware.SessionMiddleware', 55 | 'django.middleware.common.CommonMiddleware', 56 | 'django.middleware.csrf.CsrfViewMiddleware', 57 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 58 | 'django.contrib.messages.middleware.MessageMiddleware', 59 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 60 | 61 | 'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware', 62 | ] 63 | 64 | ROOT_URLCONF = 'myproject.urls' 65 | 66 | TEMPLATES = [ 67 | { 68 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 69 | 'DIRS': [os.path.join(BASE_DIR, 'templates')], 70 | 'APP_DIRS': True, 71 | 'OPTIONS': { 72 | 'context_processors': [ 73 | 'django.template.context_processors.debug', 74 | 'django.template.context_processors.request', 75 | 'django.contrib.auth.context_processors.auth', 76 | 'django.contrib.messages.context_processors.messages', 77 | 78 | 'blog.context_processors.post_tags', 79 | ], 80 | }, 81 | }, 82 | ] 83 | 84 | WSGI_APPLICATION = 'myproject.wsgi.application' 85 | 86 | 87 | # Database 88 | # https://docs.djangoproject.com/en/1.11/ref/settings/#databases 89 | 90 | DATABASES = { 91 | 'default': { 92 | 'ENGINE': 'django.db.backends.mysql', 93 | 'NAME': os.environ.get('DB_NAME'), 94 | 'USER': os.environ.get('DB_USER'), 95 | 'PASSWORD': os.environ.get('DB_PASSWORD'), 96 | 'HOST': os.environ.get('DB_HOST'), 97 | } 98 | } 99 | 100 | 101 | # Password validation 102 | # https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators 103 | 104 | AUTH_PASSWORD_VALIDATORS = [ 105 | { 106 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 107 | }, 108 | { 109 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 110 | }, 111 | { 112 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 113 | }, 114 | { 115 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 116 | }, 117 | ] 118 | 119 | 120 | # Internationalization 121 | # https://docs.djangoproject.com/en/1.11/topics/i18n/ 122 | 123 | LANGUAGE_CODE = 'ru-ru' 124 | 125 | TIME_ZONE = 'Europe/Moscow' 126 | 127 | USE_I18N = True 128 | 129 | USE_L10N = True 130 | 131 | USE_TZ = True 132 | 133 | 134 | # Static files (CSS, JavaScript, Images) 135 | # https://docs.djangoproject.com/en/1.11/howto/static-files/ 136 | 137 | STATIC_URL = '/s/' 138 | STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') 139 | MEDIA_URL = '/m/' 140 | MEDIA_ROOT = os.path.join(BASE_DIR, 'media') 141 | 142 | STATICFILES_DIRS = [ 143 | os.path.join(BASE_DIR, 'static'), 144 | ] 145 | 146 | 147 | # Email backend 148 | 149 | DEFAULT_FROM_EMAIL = 'noreply@example.com' 150 | 151 | 152 | # Sites framework 153 | 154 | SITE_ID = 1 155 | 156 | 157 | # django-ckeditor settings 158 | 159 | CKEDITOR_UPLOAD_PATH = 'uploads/' 160 | CKEDITOR_CONFIGS = { 161 | 'default': { 162 | 'width': 850, 163 | 'height': 350, 164 | 'filebrowserWindowWidth': 975, 165 | 'filebrowserWindowHeight': 550, 166 | }, 167 | 'small': { 168 | 'width': 850, 169 | 'height': 200, 170 | 'filebrowserWindowWidth': 975, 171 | 'filebrowserWindowHeight': 550, 172 | }, 173 | } 174 | 175 | 176 | if DEBUG: 177 | SECRET_KEY = 'qwerty' 178 | 179 | INSTALLED_APPS += [ 180 | 'debug_toolbar', 181 | ] 182 | 183 | MIDDLEWARE += [ 184 | 'debug_toolbar.middleware.DebugToolbarMiddleware', 185 | ] 186 | 187 | DATABASES['default']['NAME'] = 'djangoblogsample' 188 | DATABASES['default']['USER'] = 'root' 189 | DATABASES['default']['PASSWORD'] = '' 190 | DATABASES['default']['HOST'] = 'localhost' 191 | 192 | EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' 193 | 194 | 195 | # django-debug-toolbar settings 196 | 197 | DEBUG_TOOLBAR_CONFIG = { 198 | 'SHOW_TOOLBAR_CALLBACK': lambda x: True, 199 | 'INTERCEPT_REDIRECTS': False, 200 | 'ENABLE_STACKTRACES': False, 201 | } 202 | -------------------------------------------------------------------------------- /myproject/urls.py: -------------------------------------------------------------------------------- 1 | """myproject URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/1.11/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.conf.urls import url, include 14 | 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) 15 | """ 16 | 17 | from django.conf import settings 18 | from django.conf.urls import url, include 19 | from django.conf.urls.static import static 20 | from django.contrib import admin 21 | 22 | urlpatterns = [ 23 | url(r'^admin/', admin.site.urls), 24 | 25 | url(r'^ckeditor/', include('ckeditor_uploader.urls')), 26 | 27 | url(r'^contact/', include('contact.urls', namespace='contact')), 28 | url(r'^blog/', include('blog.urls', namespace='blog')), 29 | url(r'', include('core.urls', namespace='core')), 30 | ] 31 | 32 | if settings.DEBUG: 33 | import debug_toolbar 34 | 35 | urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 36 | urlpatterns += [ 37 | url(r'^__debug__/', include(debug_toolbar.urls)), 38 | ] 39 | -------------------------------------------------------------------------------- /myproject/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for myproject project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /requirements.in: -------------------------------------------------------------------------------- 1 | Django 2 | django-ckeditor 3 | mysqlclient 4 | Pillow 5 | 6 | # debug 7 | django-debug-toolbar 8 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile 3 | # To update, run: 4 | # 5 | # pip-compile --output-file requirements.txt requirements.in 6 | # 7 | django-ckeditor==5.3.0 8 | django-debug-toolbar==1.8 9 | django-js-asset==0.1.1 # via django-ckeditor 10 | django==1.11.3 # via django-debug-toolbar 11 | mysqlclient==1.3.10 12 | olefile==0.44 # via pillow 13 | pillow==4.2.1 14 | pytz==2017.2 # via django 15 | sqlparse==0.2.3 # via django-debug-toolbar 16 | -------------------------------------------------------------------------------- /sample_db_data.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "model": "flatpages.flatpage", 4 | "pk": 1, 5 | "fields": { 6 | "url": "/about/", 7 | "title": "About", 8 | "content": "

This is the About page for the site.

", 9 | "enable_comments": false, 10 | "template_name": "", 11 | "registration_required": false, 12 | "sites": [1] 13 | } 14 | } 15 | ] 16 | -------------------------------------------------------------------------------- /static/css/main.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | height: 100%; 4 | } 5 | 6 | form { 7 | margin-top: 20px; 8 | } 9 | 10 | footer { 11 | margin: 40px 0 20px; 12 | } 13 | 14 | /* bootstrap */ 15 | .breadcrumb { 16 | margin-top: -21px; 17 | margin-bottom: 10px; 18 | background-color: #fff; 19 | border: 0; 20 | border-radius: 0; 21 | } 22 | 23 | .alert { 24 | margin-bottom: 0; 25 | } 26 | 27 | .help-block { 28 | margin-bottom: 0; 29 | } 30 | 31 | .pagination { 32 | margin-top: 10px; 33 | } 34 | 35 | /* general */ 36 | .portlet { 37 | margin-bottom: 20px; 38 | padding: 10px 0 10px 15px; 39 | border-left: solid 5px #ccc; 40 | } 41 | .portlet:first-child { 42 | margin-top: 20px; 43 | } 44 | .portlet > h3 { 45 | margin: 0 0 20px; 46 | } 47 | .portlet > ul { 48 | margin: 0 0 0 20px; 49 | padding: 0; 50 | } 51 | 52 | @media (max-width: 991px) { 53 | .portlet:first-child { 54 | display: inline-block; 55 | } 56 | } 57 | 58 | /* flatpages */ 59 | .flatpages { 60 | margin-bottom: 20px; 61 | } 62 | 63 | /* contact */ 64 | .contact { 65 | margin-bottom: 20px; 66 | } 67 | 68 | /* blog */ 69 | .blog > .post { 70 | margin-bottom: 20px; 71 | } 72 | .blog > .post > .date { 73 | color: #999; 74 | margin-bottom: 20px; 75 | } 76 | .blog > .post > .tags { 77 | margin-top: 20px; 78 | } 79 | 80 | .blog > .post-archive > ul { 81 | margin-left: 40px; 82 | padding: 0; 83 | list-style: none; 84 | } 85 | .blog > .post-archive > ul > li { 86 | margin-bottom: 5px; 87 | } 88 | .blog > .post-archive > ul > li > .date { 89 | display: inline-block; 90 | margin-right: 20px; 91 | } 92 | -------------------------------------------------------------------------------- /static/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: /blog/page1/ 3 | Disallow: /blog/tags/*/page1/ 4 | -------------------------------------------------------------------------------- /templates/404.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block title %}404 Not Found · {{ block.super }}{% endblock %} 4 | 5 | {% block breadcrumb-wrapper %}{% endblock %} 6 | 7 | {% block content %} 8 |

File or Page Not Found

9 | 10 |

Sorry! What you're looking for isn't available.

11 | {% endblock content %} 12 | -------------------------------------------------------------------------------- /templates/base.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | {% block title %}Django Blog Sample{% endblock %} 13 | 14 | 15 | {% include 'includes/_header.html' %} 16 | 17 | {% block breadcrumb-wrapper %} 18 | 23 | {% endblock %} 24 | 25 |
26 | {% include 'includes/_alerts.html' %} 27 | 28 | {% block content-wrapper %} 29 |
30 |
31 | {% block content %}{% endblock %} 32 |
33 |
34 | {% endblock %} 35 | 36 | {% include 'includes/_footer.html' %} 37 |
38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /templates/blog/base.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block title %}Blog · {{ block.super }}{% endblock %} 4 | 5 | {% block content-wrapper %} 6 |
7 |
8 |
9 | {% block content %}{% endblock %} 10 |
11 |
12 | 13 |
14 | {% block sidebar-right %} 15 |
16 |

Browse

17 | 18 |
22 | 23 | {% if post_tags %} 24 |
25 |

Tags

26 | 27 |
33 | {% endif %} 34 | {% endblock %} 35 |
36 |
37 | {% endblock %} 38 | -------------------------------------------------------------------------------- /templates/blog/post_archive.html: -------------------------------------------------------------------------------- 1 | {% extends 'blog/base.html' %} 2 | 3 | {% block title %}Archive · {{ block.super }}{% endblock %} 4 | 5 | {% block breadcrumbs %} 6 | {{ block.super }} 7 |
  • Blog
  • 8 |
  • Archive
  • 9 | {% endblock %} 10 | 11 | {% block content %} 12 |
    13 |

    Archive

    14 | 15 | {% for year, posts in years.items %} 16 |

    {{ year }}

    17 | 18 |
      19 | {% for post in posts %} 20 |
    • 21 |
      22 | {{ post.published_date|date:"M d" }} 23 |
      24 | 25 | {{ post.title }} 26 |
    • 27 | {% endfor %} 28 |
    29 | {% empty %} 30 |

    No posts yet.

    31 | {% endfor %} 32 |
    33 | {% endblock content %} 34 | -------------------------------------------------------------------------------- /templates/blog/post_detail.html: -------------------------------------------------------------------------------- 1 | {% extends 'blog/base.html' %} 2 | 3 | {% block title %}{{ post.title }} · {{ block.super }}{% endblock %} 4 | 5 | {% block breadcrumbs %} 6 | {{ block.super }} 7 |
  • Blog
  • 8 |
  • {{ post.title }}
  • 9 | {% endblock %} 10 | 11 | {% block content %} 12 |
    13 |

    {{ post.title }}

    14 | 15 |
    16 | 17 | {{ post.published_date|date:"F j, Y" }} 18 |
    19 | 20 |

    {{ post.body|safe }}

    21 | 22 | {% if post.tags.all %} 23 |
    24 | {% for tag in post.tags.all %} 25 | {{ tag.name }} 26 | {% endfor %} 27 |
    28 | {% endif %} 29 |
    30 | {% endblock %} 31 | -------------------------------------------------------------------------------- /templates/blog/post_list.html: -------------------------------------------------------------------------------- 1 | {% extends 'blog/base.html' %} 2 | 3 | {% block breadcrumbs %} 4 | {{ block.super }} 5 |
  • Blog
  • 6 | {% endblock %} 7 | 8 | {% block content %} 9 | {% for post in posts %} 10 |
    11 |

    {{ post.title }}

    12 | 13 |
    14 | 15 | {{ post.published_date|date:"F j, Y" }} 16 |
    17 | 18 |

    {{ post.snippet|safe }}

    19 | 20 | Read more → 21 |
    22 | {% empty %} 23 |

    Blog

    24 | 25 |

    No posts yet.

    26 | {% endfor %} 27 | 28 | {% include 'core/includes/_pagination.html' with url_name='blog:post-list' %} 29 | {% endblock content %} 30 | -------------------------------------------------------------------------------- /templates/blog/post_list_by_tag.html: -------------------------------------------------------------------------------- 1 | {% extends 'blog/base.html' %} 2 | 3 | {% block title %}{{ tag.name }} · Tags · {{ block.super }}{% endblock %} 4 | 5 | {% block breadcrumbs %} 6 | {{ block.super }} 7 |
  • Blog
  • 8 |
  • Tags
  • 9 |
  • {{ tag.name }}
  • 10 | {% endblock %} 11 | 12 | {% block content %} 13 | {% for post in posts %} 14 |
    15 |

    {{ post.title }}

    16 | 17 |
    18 | 19 | {{ post.published_date|date:"F j, Y" }} 20 |
    21 | 22 |

    {{ post.snippet|safe }}

    23 | 24 | Read more → 25 |
    26 | {% endfor %} 27 | 28 | {% include 'core/includes/_pagination.html' with url_name='blog:post-list-by-tag' url_args=tag.slug %} 29 | {% endblock content %} 30 | -------------------------------------------------------------------------------- /templates/contact/contact.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Contact Us · {{ block.super }}{% endblock %} 4 | 5 | {% block breadcrumbs %} 6 | {{ block.super }} 7 |
  • Contact Us
  • 8 | {% endblock %} 9 | 10 | {% block content %} 11 |
    12 |

    Contact Us

    13 | 14 |

    Please fill out the following form to contact us. Thank you.

    15 | 16 |
    17 | {% csrf_token %} 18 | 19 |
    20 |
    21 |
    22 | {% include 'includes/_field.html' with field=form.name %} 23 |
    24 |
    25 |
    26 | 27 |
    28 |
    29 |
    30 | {% include 'includes/_field.html' with field=form.email %} 31 |
    32 |
    33 |
    34 | 35 |
    36 |
    37 |
    38 | {% include 'includes/_field.html' with field=form.subject %} 39 |
    40 |
    41 |
    42 | 43 |
    44 |
    45 |
    46 | {% include 'includes/_field.html' with field=form.message %} 47 |
    48 |
    49 |
    50 | 51 | 52 |
    53 |
    54 | {% endblock %} 55 | -------------------------------------------------------------------------------- /templates/core/home.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block breadcrumb-wrapper %}{% endblock %} 4 | 5 | {% block content %} 6 |
    7 |

    Hello!

    8 | 9 |

    Django-powered project was created successfully.

    10 |

    Get documentation

    11 |
    12 | {% endblock %} 13 | -------------------------------------------------------------------------------- /templates/core/includes/_pagination.html: -------------------------------------------------------------------------------- 1 | {% load core_extras %} 2 | 3 | {% if is_paginated %} 4 |
      5 | {% if page_obj.has_previous %} 6 |
    • <<
    • 7 | {% else %} 8 |
    • <<
    • 9 | {% endif %} 10 | 11 | {% for i in page_obj|create_pagination_range:"10" %} 12 | {% if page_obj.number == i %} 13 |
    • {{ i }}
    • 14 | {% else %} 15 |
    • {{ i }}
    • 16 | {% endif %} 17 | {% endfor %} 18 | 19 | {% if page_obj.has_next %} 20 |
    • >>
    • 21 | {% else %} 22 |
    • >>
    • 23 | {% endif %} 24 |
    25 | {% endif %} 26 | -------------------------------------------------------------------------------- /templates/flatpages/default.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}{{ flatpage.title }} · {{ block.super }}{% endblock %} 4 | 5 | {% block breadcrumbs %} 6 | {{ block.super }} 7 |
  • {{ flatpage.title }}
  • 8 | {% endblock %} 9 | 10 | {% block content %} 11 |
    12 |

    {{ flatpage.title }}

    13 | 14 | {{ flatpage.content }} 15 |
    16 | {% endblock %} 17 | -------------------------------------------------------------------------------- /templates/includes/_alerts.html: -------------------------------------------------------------------------------- 1 | {% for message in messages %} 2 | 7 | {% endfor %} 8 | -------------------------------------------------------------------------------- /templates/includes/_field.html: -------------------------------------------------------------------------------- 1 | {% if field.errors %} 2 |
    3 | {% endif %} 4 | 8 | 9 | {{ field }} 10 | 11 | {% if field.field.help_text %} 12 | {{ field.field.help_text }} 13 | {% endif %} 14 | 15 | {% if field.errors %} 16 | {% for error in field.errors %} 17 | {{ error }} 18 | {% endfor %} 19 | {% endif %} 20 | {% if field.errors %} 21 |
    22 | {% endif %} 23 | -------------------------------------------------------------------------------- /templates/includes/_footer.html: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /templates/includes/_header.html: -------------------------------------------------------------------------------- 1 | 28 | --------------------------------------------------------------------------------