├── blog ├── __init__.py ├── apps.py ├── admin.py ├── utils.py ├── urls.py ├── forms.py ├── models.py ├── templates │ └── blog │ │ ├── post_footer.html │ │ ├── my-blog.html │ │ ├── post_form.html │ │ ├── user-blog.html │ │ ├── single-post.html │ │ └── post_list.html └── views.py ├── config ├── __init__.py ├── asgi.py ├── wsgi.py ├── urls.py └── settings.py ├── polls ├── __init__.py ├── apps.py ├── admin.py ├── urls.py ├── models.py ├── views.py └── templates │ └── polls │ ├── questions.html │ ├── question.html │ └── results.html ├── users ├── __init__.py ├── apps.py ├── admin.py ├── signals.py ├── urls.py ├── utils.py ├── templates │ └── users │ │ ├── friends.html │ │ ├── interest_form.html │ │ ├── profile_form.html │ │ ├── message.html │ │ ├── message_form.html │ │ ├── profiles.html │ │ ├── inbox.html │ │ ├── login_register.html │ │ ├── account.html │ │ └── user-profile.html ├── forms.py └── models.py ├── quizzes ├── __init__.py ├── apps.py ├── admin.py ├── urls.py ├── templates │ └── quizzes │ │ ├── partial.html │ │ ├── quizzes.html │ │ ├── results.html │ │ └── display.html ├── models.py └── views.py ├── static ├── assets │ ├── css │ │ ├── user.min.css │ │ ├── user-rtl.min.css │ │ ├── user.css │ │ ├── user-rtl.css │ │ ├── user.min.css.map │ │ └── user-rtl.min.css.map │ ├── img │ │ ├── icons │ │ │ ├── blogs.png │ │ │ ├── polls.png │ │ │ ├── users.png │ │ │ ├── quizzes.png │ │ │ ├── blogs-hover.png │ │ │ ├── polls-hover.png │ │ │ ├── quizzes-hover.png │ │ │ └── users-hover.png │ │ ├── gallery │ │ │ ├── blogs.jpg │ │ │ ├── polls.jpg │ │ │ ├── users.jpg │ │ │ └── quizzes.jpg │ │ └── illustrations │ │ │ ├── bg.png │ │ │ ├── bg-left.png │ │ │ ├── circle.png │ │ │ ├── hero-header-bg.png │ │ │ ├── bg-car-insurance.png │ │ │ └── bg-car-insurancee.png │ └── js │ │ └── theme.min.js ├── favicon.ico └── vendors │ ├── prism │ └── prism.css │ ├── anchorjs │ └── anchor.min.js │ ├── rellax │ └── rellax.min.js │ └── gsap │ └── scrollToPlugin.js ├── media └── profile_images │ ├── 1.jpg │ ├── 2.jpg │ ├── 3.jpg │ ├── 4.jpg │ ├── max.png │ ├── andy.png │ ├── egord.png │ ├── elenaa.jpg │ ├── ella.png │ ├── mary.png │ ├── default.jpg │ └── 7-yIj-GpUkU.jpg ├── requirements.txt ├── manage.py ├── templates ├── tags_categories.html ├── socials.html ├── pagination.html ├── delete_template.html ├── navbar.html └── base.html └── README.md /blog/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /config/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /polls/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /users/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /quizzes/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/assets/css/user.min.css: -------------------------------------------------------------------------------- 1 | 2 | /*# sourceMappingURL=user.min.css.map */ 3 | -------------------------------------------------------------------------------- /static/assets/css/user-rtl.min.css: -------------------------------------------------------------------------------- 1 | 2 | /*# sourceMappingURL=user-rtl.min.css.map */ 3 | -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natkaida/blogit/HEAD/static/favicon.ico -------------------------------------------------------------------------------- /static/assets/css/user.css: -------------------------------------------------------------------------------- 1 | /* prettier-ignore */ 2 | /*# sourceMappingURL=user.css.map */ 3 | -------------------------------------------------------------------------------- /media/profile_images/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natkaida/blogit/HEAD/media/profile_images/1.jpg -------------------------------------------------------------------------------- /media/profile_images/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natkaida/blogit/HEAD/media/profile_images/2.jpg -------------------------------------------------------------------------------- /media/profile_images/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natkaida/blogit/HEAD/media/profile_images/3.jpg -------------------------------------------------------------------------------- /media/profile_images/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natkaida/blogit/HEAD/media/profile_images/4.jpg -------------------------------------------------------------------------------- /static/assets/css/user-rtl.css: -------------------------------------------------------------------------------- 1 | /* prettier-ignore */ 2 | /*# sourceMappingURL=user-rtl.css.map */ 3 | -------------------------------------------------------------------------------- /media/profile_images/max.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natkaida/blogit/HEAD/media/profile_images/max.png -------------------------------------------------------------------------------- /media/profile_images/andy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natkaida/blogit/HEAD/media/profile_images/andy.png -------------------------------------------------------------------------------- /media/profile_images/egord.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natkaida/blogit/HEAD/media/profile_images/egord.png -------------------------------------------------------------------------------- /media/profile_images/elenaa.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natkaida/blogit/HEAD/media/profile_images/elenaa.jpg -------------------------------------------------------------------------------- /media/profile_images/ella.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natkaida/blogit/HEAD/media/profile_images/ella.png -------------------------------------------------------------------------------- /media/profile_images/mary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natkaida/blogit/HEAD/media/profile_images/mary.png -------------------------------------------------------------------------------- /media/profile_images/default.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natkaida/blogit/HEAD/media/profile_images/default.jpg -------------------------------------------------------------------------------- /static/assets/img/icons/blogs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natkaida/blogit/HEAD/static/assets/img/icons/blogs.png -------------------------------------------------------------------------------- /static/assets/img/icons/polls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natkaida/blogit/HEAD/static/assets/img/icons/polls.png -------------------------------------------------------------------------------- /static/assets/img/icons/users.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natkaida/blogit/HEAD/static/assets/img/icons/users.png -------------------------------------------------------------------------------- /media/profile_images/7-yIj-GpUkU.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natkaida/blogit/HEAD/media/profile_images/7-yIj-GpUkU.jpg -------------------------------------------------------------------------------- /static/assets/img/gallery/blogs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natkaida/blogit/HEAD/static/assets/img/gallery/blogs.jpg -------------------------------------------------------------------------------- /static/assets/img/gallery/polls.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natkaida/blogit/HEAD/static/assets/img/gallery/polls.jpg -------------------------------------------------------------------------------- /static/assets/img/gallery/users.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natkaida/blogit/HEAD/static/assets/img/gallery/users.jpg -------------------------------------------------------------------------------- /static/assets/img/icons/quizzes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natkaida/blogit/HEAD/static/assets/img/icons/quizzes.png -------------------------------------------------------------------------------- /static/assets/css/user.min.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":[],"names":[],"mappings":"","file":"user.min.css","sourcesContent":[]} -------------------------------------------------------------------------------- /static/assets/img/gallery/quizzes.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natkaida/blogit/HEAD/static/assets/img/gallery/quizzes.jpg -------------------------------------------------------------------------------- /static/assets/img/illustrations/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natkaida/blogit/HEAD/static/assets/img/illustrations/bg.png -------------------------------------------------------------------------------- /static/assets/img/icons/blogs-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natkaida/blogit/HEAD/static/assets/img/icons/blogs-hover.png -------------------------------------------------------------------------------- /static/assets/img/icons/polls-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natkaida/blogit/HEAD/static/assets/img/icons/polls-hover.png -------------------------------------------------------------------------------- /static/assets/img/icons/quizzes-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natkaida/blogit/HEAD/static/assets/img/icons/quizzes-hover.png -------------------------------------------------------------------------------- /static/assets/img/icons/users-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natkaida/blogit/HEAD/static/assets/img/icons/users-hover.png -------------------------------------------------------------------------------- /static/assets/css/user-rtl.min.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":[],"names":[],"mappings":"","file":"user-rtl.min.css","sourcesContent":[]} -------------------------------------------------------------------------------- /static/assets/img/illustrations/bg-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natkaida/blogit/HEAD/static/assets/img/illustrations/bg-left.png -------------------------------------------------------------------------------- /static/assets/img/illustrations/circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natkaida/blogit/HEAD/static/assets/img/illustrations/circle.png -------------------------------------------------------------------------------- /static/assets/img/illustrations/hero-header-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natkaida/blogit/HEAD/static/assets/img/illustrations/hero-header-bg.png -------------------------------------------------------------------------------- /static/assets/img/illustrations/bg-car-insurance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natkaida/blogit/HEAD/static/assets/img/illustrations/bg-car-insurance.png -------------------------------------------------------------------------------- /static/assets/img/illustrations/bg-car-insurancee.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natkaida/blogit/HEAD/static/assets/img/illustrations/bg-car-insurancee.png -------------------------------------------------------------------------------- /blog/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class BlogConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'blog' 7 | -------------------------------------------------------------------------------- /polls/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class PollsConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'polls' 7 | -------------------------------------------------------------------------------- /quizzes/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class QuizzesConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'quizzes' 7 | -------------------------------------------------------------------------------- /blog/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Post, Category, Tag, Comment 3 | 4 | admin.site.register(Category) 5 | admin.site.register(Comment) 6 | admin.site.register(Tag) 7 | admin.site.register(Post) 8 | -------------------------------------------------------------------------------- /users/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class UsersConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'users' 7 | 8 | def ready(self): 9 | import users.signals 10 | -------------------------------------------------------------------------------- /users/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Profile, Interest, Message 3 | class InterestInline(admin.TabularInline): 4 | model = Interest 5 | extra = 3 6 | 7 | class ProfileAdmin(admin.ModelAdmin): 8 | inlines = [InterestInline] 9 | 10 | 11 | admin.site.register(Message) 12 | admin.site.register(Interest) 13 | admin.site.register(Profile, ProfileAdmin) -------------------------------------------------------------------------------- /polls/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Question, Choice, Vote 3 | 4 | class ChoiceInLine(admin.TabularInline): 5 | model = Choice 6 | extra = 3 7 | 8 | 9 | class QuestionAdmin(admin.ModelAdmin): 10 | fieldsets = [(None, {'fields': ['name']}),] 11 | inlines = [ChoiceInLine] 12 | 13 | admin.site.register(Question, QuestionAdmin) 14 | admin.site.register(Choice) 15 | admin.site.register(Vote) -------------------------------------------------------------------------------- /polls/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from . import views 3 | 4 | app_name = 'polls' 5 | urlpatterns = [ 6 | path('polls/', views.questions, name='questions'), 7 | path('question//', 8 | views.question, name='question'), 9 | path('question//results/', 10 | views.results, name='results'), 11 | path('question//vote/', 12 | views.vote, name='vote'), 13 | ] -------------------------------------------------------------------------------- /config/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for config project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.1/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /config/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for config 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/4.1/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', 'config.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /quizzes/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Quiz, Question, Answer, Choice, Result 3 | 4 | class AnswerInline(admin.TabularInline): 5 | model = Answer 6 | extra = 3 7 | 8 | class QuestionAdmin(admin.ModelAdmin): 9 | inlines = [AnswerInline] 10 | 11 | admin.site.register(Quiz) 12 | admin.site.register(Answer) 13 | admin.site.register(Question, QuestionAdmin) 14 | admin.site.register(Choice) 15 | admin.site.register(Result) 16 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | asgiref==3.6.0 2 | backports.zoneinfo;python_version<"3.9" 3 | click==8.1.3 4 | colorama==0.4.6 5 | cssbeautifier==1.14.7 6 | Django==4.1.7 7 | djlint==1.19.16 8 | EditorConfig==0.12.3 9 | html-tag-names==0.1.2 10 | html-void-elements==0.1.0 11 | jsbeautifier==1.14.7 12 | pathspec==0.11.1 13 | Pillow==9.4.0 14 | pytils==0.4.1 15 | PyYAML==6.0 16 | regex==2022.10.31 17 | ruff==0.0.259 18 | six==1.16.0 19 | sqlparse==0.4.3 20 | tomli==2.0.1 21 | tqdm==4.65.0 22 | tzdata==2022.7 23 | -------------------------------------------------------------------------------- /config/urls.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.urls import path, include 3 | from django.conf import settings 4 | from django.conf.urls.static import static 5 | 6 | urlpatterns = [ 7 | path('admin/', admin.site.urls), 8 | path('', include('blog.urls')), 9 | path('', include('users.urls')), 10 | path('', include('polls.urls')), 11 | path('', include('quizzes.urls')), 12 | ] 13 | 14 | urlpatterns += static(settings.MEDIA_URL, 15 | document_root=settings.MEDIA_ROOT) 16 | urlpatterns += static(settings.STATIC_URL, 17 | document_root=settings.STATIC_ROOT) -------------------------------------------------------------------------------- /quizzes/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from . import views 4 | 5 | app_name = 'quizzes' 6 | 7 | urlpatterns = [ 8 | path('quizzes/', views.quizzes, name='quizzes'), 9 | path('/', views.display_quiz, 10 | name='display_quiz'), 11 | path('/questions/', 12 | views.display_question, name='display_question'), 13 | path('/questions//grade/', 14 | views.grade_question, name='grade_question'), 15 | path('results//', views.quiz_results, 16 | name='quiz_results'), 17 | 18 | ] 19 | 20 | 21 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /blog/utils.py: -------------------------------------------------------------------------------- 1 | from .models import Post, Tag 2 | from django.db.models import Q 3 | from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage 4 | 5 | 6 | def searchPosts(request): 7 | profile = request.user.profile 8 | search_query = '' 9 | 10 | if request.GET.get('search_query'): 11 | search_query = request.GET.get('search_query') 12 | 13 | tags = Tag.objects.filter(name__icontains=search_query) 14 | 15 | posts = Post.objects.filter( 16 | owner__in=profile.follows.all() 17 | ) 18 | posts = posts.distinct().filter( 19 | Q(title__icontains=search_query) | 20 | Q(text__icontains=search_query) | 21 | Q(owner__name__icontains=search_query) | 22 | Q(tags__in=tags) 23 | ) 24 | return posts, search_query 25 | -------------------------------------------------------------------------------- /templates/tags_categories.html: -------------------------------------------------------------------------------- 1 |
2 |
Категории
3 | 11 |
12 |
13 |
Теги
14 |
15 | {% for tag in tags %} 16 | {{ tag.name }} 17 | {% endfor %} 18 |
19 |
20 | -------------------------------------------------------------------------------- /blog/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from . import views 3 | 4 | app_name = "blog" 5 | 6 | urlpatterns = [ 7 | path('blog//', views.userBlog, name="user-blog"), 8 | path('myblog/', views.myBlog, name="my-blog"), 9 | path('friends/', views.friends, name="friends"), 10 | path('post//', views.post, name="post"), 11 | path('category//', views.posts_by_category, name="by_category"), 12 | path('tag/', views.posts_by_tag, name="by_tag"), 13 | path('create-post/', views.createPost, name="create-post"), 14 | path('update-post/', views.updatePost, name="update-post"), 15 | path('delete-post/', views.deletePost, name="delete-post"), 16 | path('like/', views.like_post, name='like_post'), 17 | path('bookmark/', views.bookmark_post, name='bookmark_post'), 18 | path('bookmarks/', views.user_bookmarks, name='user_bookmarks'), 19 | ] -------------------------------------------------------------------------------- /quizzes/templates/quizzes/partial.html: -------------------------------------------------------------------------------- 1 | {% if error_message %} 2 | {{ error_message }} 3 | {% elif is_correct %} 4 | Правильно. 5 | {{ question.explanation }} 6 | {% elif is_correct == None %} 7 | Не выбран вариант ответа. Не забудьте ответить позже! 8 | {% else %} 9 | {% if correct_answer|length > 1 %} 10 |
11 | Неверно! Правильные ответы: 12 |
    13 | {% for answer in correct_answer %}
  • {{ answer }}
  • {% endfor %} 14 |
15 |
16 | {% else %} 17 | Неверно! Правильный ответ: {{ correct_answer }} 18 | {% endif %} 19 | {{ question.explanation }} 20 | {% endif %} 21 | -------------------------------------------------------------------------------- /templates/socials.html: -------------------------------------------------------------------------------- 1 |
    2 |
  • 3 | 4 |
  • 5 |
  • 6 | 7 |
  • 8 |
  • 9 | 10 |
  • 11 |
  • 12 | 13 |
  • 14 |
  • 15 | 16 |
  • 17 |
  • 18 | 19 |
  • 20 |
21 | -------------------------------------------------------------------------------- /users/signals.py: -------------------------------------------------------------------------------- 1 | from django.db.models.signals import post_save, post_delete 2 | from django.dispatch import receiver 3 | from django.contrib.auth.models import User 4 | from .models import Profile 5 | from django.conf import settings 6 | 7 | 8 | def createProfile(sender, instance, created, **kwargs): 9 | if created: 10 | user = instance 11 | profile = Profile.objects.create( 12 | user=user, 13 | username=user.username, 14 | email=user.email, 15 | name=user.first_name, 16 | ) 17 | 18 | 19 | def updateUser(sender, instance, created, **kwargs): 20 | profile = instance 21 | user = profile.user 22 | 23 | if created == False: 24 | user.first_name = profile.name 25 | user.username = profile.username 26 | user.email = profile.email 27 | user.save() 28 | 29 | 30 | def deleteUser(sender, instance, **kwargs): 31 | try: 32 | user = instance.user 33 | user.delete() 34 | except: 35 | pass 36 | 37 | post_save.connect(createProfile, sender=User) 38 | post_save.connect(updateUser, sender=Profile) 39 | post_delete.connect(deleteUser, sender=Profile) -------------------------------------------------------------------------------- /users/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from . import views 3 | 4 | urlpatterns = [ 5 | 6 | path('', views.landing, name='landing'), 7 | path('profiles/', views.profiles, name='profiles'), 8 | path('landing/', views.landingLogin, name='landingLogin'), 9 | path('login/', views.loginUser, name='login'), 10 | path('logout/', views.logoutUser, name='logout'), 11 | path('register/', views.registerUser, name='register'), 12 | path('profile//', views.userProfile, 13 | name='user-profile'), 14 | path('follow//', views.follow_unfollow, 15 | name='follow-unfollow'), 16 | path('account/', views.userAccount, name='account'), 17 | path('edit-account/', views.editAccount, 18 | name='edit-account'), 19 | path('create-interest/', views.createInterest, 20 | name='create-interest'), 21 | path('update-interest//', 22 | views.updateInterest, name='update-interest'), 23 | path('delete-interest//', 24 | views.deleteInterest, name='delete-interest'), 25 | path('interest/', 26 | views.profiles_by_interest, name='interest'), 27 | path('inbox/', views.inbox, name='inbox'), 28 | path('message//', views.viewMessage, name='message'), 29 | path('create-message//', 30 | views.createMessage, name='create-message'), 31 | 32 | ] -------------------------------------------------------------------------------- /blog/forms.py: -------------------------------------------------------------------------------- 1 | from django.forms import ModelForm 2 | from django import forms 3 | from .models import Post, Comment, Category 4 | 5 | class PostForm(ModelForm): 6 | class Meta: 7 | model = Post 8 | fields = ['title', 'category', 9 | 'tags', 'text'] 10 | labels = { 11 | 'title': 'Название', 12 | 'category':'Категория', 13 | 'tags':'Теги', 14 | 'text':'Текст', 15 | } 16 | 17 | widgets = { 18 | 'tags': forms.CheckboxSelectMultiple(), 19 | } 20 | category = forms.ModelChoiceField(queryset=Category.objects.none(), 21 | empty_label="(Работа)") 22 | def __init__(self, *args, **kwargs): 23 | super(PostForm, self).__init__(*args, **kwargs) 24 | 25 | for name, field in self.fields.items(): 26 | field.widget.attrs.update({'class': 'form-control input-box form-ensurance-header-control'}) 27 | self.fields['category'].empty_label = None 28 | 29 | 30 | class CommentForm(ModelForm): 31 | class Meta: 32 | model = Comment 33 | fields = ['text'] 34 | 35 | labels = { 36 | 'text': 'Ваш комментарий' 37 | } 38 | 39 | def __init__(self, *args, **kwargs): 40 | super(CommentForm, self).__init__(*args, **kwargs) 41 | 42 | for name, field in self.fields.items(): 43 | field.widget.attrs.update({'class': 'form-control input-box form-ensurance-header-control'}) -------------------------------------------------------------------------------- /templates/pagination.html: -------------------------------------------------------------------------------- 1 | {% if queryset.has_other_pages %} 2 |
3 |
    4 | {% if queryset.has_previous %} 5 |
  • 6 | ❮ Предыдущая 9 |
  • 10 | {% endif %} 11 | {% for page in custom_range %} 12 | {% if page == queryset.number %} 13 |
  • 14 | {{ page }} 15 |
  • 16 | {% else %} 17 |
  • 18 | {{ page }} 19 |
  • 20 | {% endif %} 21 | {% endfor %} 22 | {% if queryset.has_next %} 23 |
  • 24 | Следующая ❯ 27 |
  • 28 | {% endif %} 29 |
30 |
31 | {% endif %} 32 | -------------------------------------------------------------------------------- /polls/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.auth.models import User 3 | 4 | 5 | class Question(models.Model): 6 | name = models.CharField(max_length=300) 7 | published = models.DateTimeField(auto_now_add=True) 8 | 9 | def user_voted(self, user): 10 | user_votes = user.vote_set.all() 11 | done = user_votes.filter(question=self) 12 | if done.exists(): 13 | return False 14 | return True 15 | 16 | class Meta: 17 | ordering = ['published'] 18 | verbose_name = 'Вопрос' 19 | verbose_name_plural = 'Вопросы' 20 | 21 | def __str__(self): 22 | return self.name 23 | 24 | 25 | class Choice(models.Model): 26 | question = models.ForeignKey(Question, 27 | on_delete=models.CASCADE) 28 | name = models.CharField(max_length=200) 29 | votes = models.IntegerField(default=0) 30 | 31 | class Meta: 32 | verbose_name = 'Варианты' 33 | verbose_name_plural = 'Варианты' 34 | 35 | def __str__(self): 36 | return self.name 37 | 38 | class Vote(models.Model): 39 | user = models.ForeignKey(User, 40 | on_delete=models.CASCADE) 41 | question = models.ForeignKey(Question, 42 | on_delete=models.CASCADE) 43 | choice = models.ForeignKey(Choice, 44 | on_delete=models.CASCADE) 45 | 46 | class Meta: 47 | verbose_name = 'Голосование' 48 | verbose_name_plural = 'Голосования' 49 | 50 | def __str__(self): 51 | return f'{self.question.name[:15]} - {self.choice.name[:15]} - {self.user.username}' 52 | 53 | -------------------------------------------------------------------------------- /templates/delete_template.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block content %} 3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | {% csrf_token %} 13 |

Вы действительно хотите удалить "{{ object }}"?

14 |
15 | Отменить 17 | 20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | {% endblock %} 31 | -------------------------------------------------------------------------------- /users/utils.py: -------------------------------------------------------------------------------- 1 | from .models import Profile, Interest 2 | from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage 3 | from django.db.models import Q 4 | 5 | 6 | def paginateObjects(request, objects, results): 7 | page = request.GET.get('page') 8 | paginator = Paginator(objects, results) 9 | 10 | try: 11 | objects = paginator.page(page) 12 | except PageNotAnInteger: 13 | page = 1 14 | objects = paginator.page(page) 15 | except EmptyPage: 16 | page = paginator.num_pages 17 | objects = paginator.page(page) 18 | 19 | leftIndex = (int(page) - 4) 20 | 21 | if leftIndex < 1: 22 | leftIndex = 1 23 | 24 | rightIndex = (int(page) + 5) 25 | 26 | if rightIndex > paginator.num_pages: 27 | rightIndex = paginator.num_pages + 1 28 | 29 | custom_range = range(leftIndex, rightIndex) 30 | 31 | return custom_range, objects 32 | 33 | 34 | def searchProfiles(request): 35 | search_query = '' 36 | if request.GET.get('search_query'): 37 | search_query = request.GET.get('search_query') 38 | 39 | interest = Interest.objects.filter(name__icontains=search_query) 40 | if request.user.is_authenticated: 41 | profiles = Profile.objects.exclude(user=request.user).distinct().filter( 42 | Q(name__icontains=search_query) | 43 | Q(summary__icontains=search_query) | 44 | Q(interest__in=interest) 45 | ) 46 | else: 47 | profiles = Profile.objects.distinct().filter( 48 | Q(name__icontains=search_query) | 49 | Q(summary__icontains=search_query) | 50 | Q(interest__in=interest) 51 | ) 52 | return profiles, search_query 53 | 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Blogit 2 | 3 | ### Проект выполнен для Selectel: 4 | 5 | [Первая часть туториала - разработка.](https://selectel.ru/blog/tutorials/django-blog/) 6 | 7 | [Вторая часть - деплой на Gunicorn/Nginx.](https://selectel.ru/blog/tutorials/django-blog-2/) 8 | 9 | Оригинальная версия Blogit с контактной формой для отправки сообщений через SMTP Yandex - [здесь.](https://github.com/natkaida/blogit_with_contact_form) 10 | 11 | **Blogit** - многофункциональная соцплатформа на фреймворке Django. Включает в себя приложения: 12 | - **users** - создание, редактирование, удаление пользовательских профилей. Аутентификация, авторизация. Добавление в друзья (и удаление из друзей). Мессенджер. 13 | - **blog** - создание, редактирование, удаление записей. Добавление в "Избранное" и "Закладки". Вывод записей по тегам и по категориям. Комментарии, френдлента. 14 | - **polls** - проведение опросов и голосований. Визуализация результатов с Chart.js. 15 | - **quizzes** - проведение тестов. Вопросы с одним и с несколькими верными вариантами ответа. 16 | 17 | Установка: 18 | - Создайте виртуальное окружение ```python -m venv blogit\venv``` и перейдите в директорию blogit ```cd blogit```. 19 | - Активируйте окружение ```venv\scripts\activate```. 20 | - Установите зависимости ```pip install -r requirements.txt``` 21 | - Создайте базу данных и аккаунт администратора: 22 | ``` 23 | manage.py migrate 24 | manage.py createsuperuser 25 | ``` 26 | ### В случае появления ошибки ```OperationalError: no such table: users_profile```: 27 | 28 | На Windows выполните: 29 | ``` 30 | manage.py migrate auth 31 | manage.py migrate --run-syncdb 32 | ``` 33 | На Ubuntu: 34 | 35 | ``` 36 | python3 manage.py migrate auth 37 | python3 manage.py migrate --run-syncdb 38 | ``` -------------------------------------------------------------------------------- /users/templates/users/friends.html: -------------------------------------------------------------------------------- 1 | {% for profile in profiles %} 2 |
3 |
4 |
5 |
{{ profile.name }}
6 |
7 | {{ profile.city }} 8 |
9 |
10 |
11 | 12 | profile 13 | 14 |
15 |
16 |
17 |
18 |
19 | {% csrf_token %} 20 | {% if profile in user.profile.follows.all %} 21 | 27 | {% else %} 28 | 34 | {% endif %} 35 |
36 |
37 |
38 |
39 |
40 |
41 | {% endfor %} 42 | -------------------------------------------------------------------------------- /users/templates/users/interest_form.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block content %} 3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
14 | {% csrf_token %} 15 |
16 | 21 | {% for field in form %} 22 |
23 | 24 | {{ field }} 25 |
26 | {% endfor %} 27 | {% for error in field.errors %}

{{ error }}

{% endfor %} 28 |
29 | 30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | {% endblock %} 42 | -------------------------------------------------------------------------------- /quizzes/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.auth.models import User 3 | 4 | 5 | class Quiz(models.Model): 6 | name = models.CharField(max_length=120) 7 | published = models.DateTimeField(auto_now_add=True) 8 | 9 | def __str__(self): 10 | return self.name 11 | 12 | class Meta: 13 | ordering = ['published'] 14 | verbose_name = 'Тест' 15 | verbose_name_plural = 'Тесты' 16 | 17 | class Question(models.Model): 18 | class qtype(models.TextChoices): 19 | single = 'single' 20 | multiple = 'multiple' 21 | 22 | name = models.CharField(max_length=350) 23 | qtype = models.CharField(max_length=8, choices=qtype.choices, default=qtype.single) 24 | quiz = models.ForeignKey(Quiz, on_delete=models.CASCADE) 25 | explanation = models.CharField(max_length=550) 26 | 27 | 28 | def get_answers(self): 29 | if self.qtype == 'single': 30 | return self.answer_set.filter(is_correct=True).first() 31 | else: 32 | qs = self.answer_set.filter(is_correct=True).values() 33 | return [i.get('name') for i in qs] 34 | 35 | 36 | def user_can_answer(self, user): 37 | user_choices = user.choice_set.all() 38 | done = user_choices.filter(question=self) 39 | print(done) 40 | if done.exists(): 41 | return False 42 | return True 43 | 44 | def __str__(self): 45 | return self.name 46 | 47 | class Meta: 48 | ordering = ['id'] 49 | verbose_name = 'Вопрос' 50 | verbose_name_plural = 'Вопросы' 51 | 52 | class Answer(models.Model): 53 | question = models.ForeignKey(Question, on_delete=models.CASCADE) 54 | name = models.CharField(max_length=200) 55 | is_correct = models.BooleanField(default=False) 56 | 57 | def __str__(self): 58 | return self.name 59 | 60 | class Meta: 61 | verbose_name = 'Ответ' 62 | verbose_name_plural = 'Ответы' 63 | 64 | class Choice(models.Model): 65 | user = models.ForeignKey(User, on_delete=models.CASCADE) 66 | question = models.ForeignKey(Question, on_delete=models.CASCADE) 67 | answer = models.ForeignKey(Answer, on_delete=models.CASCADE) 68 | 69 | class Result(models.Model): 70 | quiz = models.ForeignKey(Quiz, on_delete=models.CASCADE) 71 | user = models.ForeignKey(User, on_delete=models.CASCADE) 72 | correct = models.IntegerField(default=0) 73 | wrong = models.IntegerField(default=0) 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /users/templates/users/profile_form.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block content %} 3 | 4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
16 | {% csrf_token %} 17 |
18 |
19 | Blogit! 20 |
21 |

Добро пожаловать на сайт

22 | {% for field in form %} 23 |
24 | 25 | {{ field }} 26 |
27 | {% endfor %} 28 | {% for error in field.errors %}

{{ error }}

{% endfor %} 29 |
30 | 31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | {% endblock content %} 43 | -------------------------------------------------------------------------------- /polls/views.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.decorators import login_required 2 | from django.http import HttpResponseRedirect 3 | from django.shortcuts import get_object_or_404, render 4 | from django.urls import reverse 5 | from .models import Question, Vote 6 | from django.contrib import messages 7 | from users.utils import paginateObjects 8 | 9 | @login_required() 10 | def questions(request): 11 | profile = request.user.profile 12 | questions = Question.objects.all() 13 | custom_range, questions = paginateObjects(request, questions, 3) 14 | context = {'questions': questions, 'custom_range': custom_range, 'profile': profile} 15 | return render(request, 'polls/questions.html', context) 16 | 17 | @login_required() 18 | def question(request, question_id): 19 | profile = request.user.profile 20 | question = Question.objects.get(pk=question_id) 21 | context = {'question': question, 22 | 'profile': profile} 23 | return render(request, 24 | 'polls/question.html', context) 25 | 26 | @login_required() 27 | def results(request, question_id): 28 | profile = request.user.profile 29 | question = get_object_or_404(Question, pk=question_id) 30 | labels = [] 31 | data = [] 32 | votes = question.choice_set.select_related('question').all() 33 | for item in votes: 34 | labels.append(item.name) 35 | data.append(item.votes) 36 | context = {'question': question, 37 | 'profile': profile, 38 | 'labels': labels, 39 | 'data': data} 40 | return render(request, 41 | 'polls/results.html', context) 42 | 43 | @login_required() 44 | def vote(request, question_id): 45 | profile = request.user.profile 46 | question = get_object_or_404(Question, pk=question_id) 47 | try: 48 | user_choice = question.choice_set.get(pk=request.POST['choice']) 49 | if not question.user_voted(request.user): 50 | messages.error(request, 51 | 'Вы уже голосовали в этом опросе.') 52 | return render(request, 53 | 'polls/question.html', 54 | {'question': question,'profile': profile}) 55 | if user_choice: 56 | user_choice.votes += 1 57 | user_choice.save() 58 | vote = Vote(user=request.user, question=question, choice=user_choice) 59 | vote.save() 60 | return HttpResponseRedirect(reverse('polls:results', args=(question.id,))) 61 | except (KeyError, UnboundLocalError): 62 | messages.error(request, 'Вы не выбрали вариант ответа!') 63 | return render(request, 'polls/question.html', {'question': question}) 64 | return render(request, 65 | 'polls/results.html', 66 | {'question': question, 'profile': profile}) 67 | -------------------------------------------------------------------------------- /static/vendors/prism/prism.css: -------------------------------------------------------------------------------- 1 | /** 2 | * prism.js default theme for JavaScript, CSS and HTML 3 | * Based on dabblet (http://dabblet.com) 4 | * @author Lea Verou 5 | */ 6 | 7 | code[class*="language-"], 8 | pre[class*="language-"] { 9 | color: black; 10 | background: none; 11 | text-shadow: 0 1px white; 12 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 13 | font-size: 1em; 14 | text-align: left; 15 | white-space: pre; 16 | word-spacing: normal; 17 | word-break: normal; 18 | word-wrap: normal; 19 | line-height: 1.5; 20 | 21 | -moz-tab-size: 4; 22 | -o-tab-size: 4; 23 | tab-size: 4; 24 | 25 | -webkit-hyphens: none; 26 | -moz-hyphens: none; 27 | -ms-hyphens: none; 28 | hyphens: none; 29 | } 30 | 31 | pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, 32 | code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { 33 | text-shadow: none; 34 | background: #b3d4fc; 35 | } 36 | 37 | pre[class*="language-"]::selection, pre[class*="language-"] ::selection, 38 | code[class*="language-"]::selection, code[class*="language-"] ::selection { 39 | text-shadow: none; 40 | background: #b3d4fc; 41 | } 42 | 43 | @media print { 44 | code[class*="language-"], 45 | pre[class*="language-"] { 46 | text-shadow: none; 47 | } 48 | } 49 | 50 | /* Code blocks */ 51 | pre[class*="language-"] { 52 | padding: 1em; 53 | margin: .5em 0; 54 | overflow: auto; 55 | } 56 | 57 | :not(pre) > code[class*="language-"], 58 | pre[class*="language-"] { 59 | background: #f5f2f0; 60 | } 61 | 62 | /* Inline code */ 63 | :not(pre) > code[class*="language-"] { 64 | padding: .1em; 65 | border-radius: .3em; 66 | white-space: normal; 67 | } 68 | 69 | .token.comment, 70 | .token.prolog, 71 | .token.doctype, 72 | .token.cdata { 73 | color: slategray; 74 | } 75 | 76 | .token.punctuation { 77 | color: #999; 78 | } 79 | 80 | .token.namespace { 81 | opacity: .7; 82 | } 83 | 84 | .token.property, 85 | .token.tag, 86 | .token.boolean, 87 | .token.number, 88 | .token.constant, 89 | .token.symbol, 90 | .token.deleted { 91 | color: #905; 92 | } 93 | 94 | .token.selector, 95 | .token.attr-name, 96 | .token.string, 97 | .token.char, 98 | .token.builtin, 99 | .token.inserted { 100 | color: #690; 101 | } 102 | 103 | .token.operator, 104 | .token.entity, 105 | .token.url, 106 | .language-css .token.string, 107 | .style .token.string { 108 | color: #9a6e3a; 109 | /* This background color was intended by the author of this theme. */ 110 | background: hsla(0, 0%, 100%, .5); 111 | } 112 | 113 | .token.atrule, 114 | .token.attr-value, 115 | .token.keyword { 116 | color: #07a; 117 | } 118 | 119 | .token.function, 120 | .token.class-name { 121 | color: #DD4A68; 122 | } 123 | 124 | .token.regex, 125 | .token.important, 126 | .token.variable { 127 | color: #e90; 128 | } 129 | 130 | .token.important, 131 | .token.bold { 132 | font-weight: bold; 133 | } 134 | .token.italic { 135 | font-style: italic; 136 | } 137 | 138 | .token.entity { 139 | cursor: help; 140 | } 141 | -------------------------------------------------------------------------------- /templates/navbar.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 62 | -------------------------------------------------------------------------------- /users/forms.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.forms import UserCreationForm 2 | from django.contrib.auth.models import User 3 | from .models import Profile, Interest, Message 4 | from django.forms import ModelForm, FileInput 5 | 6 | 7 | class CustomUserCreationForm(UserCreationForm): 8 | class Meta: 9 | model = User 10 | fields = ['first_name', 'email', 'username', 11 | 'password1', 'password2'] 12 | labels = { 13 | 'first_name': 'Имя и фамилия', 14 | 'email': 'Email', 15 | 'username':'Логин', 16 | 'password1':'Пароль', 17 | 'password2': 'Подтверждение пароля' 18 | 19 | } 20 | 21 | def __init__(self, *args, **kwargs): 22 | super(CustomUserCreationForm, self).__init__(*args, **kwargs) 23 | 24 | for name, field in self.fields.items(): 25 | field.widget.attrs.update({'class': 'form-control input-box form-ensurance-header-control'}) 26 | 27 | 28 | class ProfileForm(ModelForm): 29 | class Meta: 30 | model = Profile 31 | fields = ['name', 'email', 'username', 32 | 'summary', 'about', 'image', 33 | 'city', 'profession', 34 | 'github', 'linkedin', 'twitter', 35 | 'youtube'] 36 | labels = { 37 | 'name': 'Имя и фамилия', 38 | 'email': 'Email', 39 | 'username':'Логин', 40 | 'city': 'Город', 41 | 'profession': 'Профессия', 42 | 'summary': 'В двух словах о себе', 43 | 'about': 'Подробнее о себе', 44 | 'image': 'Изменить фото профиля', 45 | } 46 | widgets = { 47 | 'image': FileInput(), 48 | } 49 | def __init__(self, *args, **kwargs): 50 | super(ProfileForm, self).__init__(*args, **kwargs) 51 | 52 | for name, field in self.fields.items(): 53 | field.widget.attrs.update({'class': 'form-control input-box form-ensurance-header-control'}) 54 | 55 | 56 | class InterestForm(ModelForm): 57 | class Meta: 58 | model = Interest 59 | fields = '__all__' 60 | exclude = ['profile', 'slug'] 61 | 62 | labels = { 63 | 'name': 'Название', 64 | 'description':'Описание', 65 | 66 | } 67 | 68 | def __init__(self, *args, **kwargs): 69 | super(InterestForm, self).__init__(*args, **kwargs) 70 | 71 | for name, field in self.fields.items(): 72 | field.widget.attrs.update({'class': 'form-control input-box form-ensurance-header-control'}) 73 | 74 | 75 | 76 | 77 | class MessageForm(ModelForm): 78 | class Meta: 79 | model = Message 80 | fields = ['name', 'email', 'subject', 'body'] 81 | labels = {'name': 'Имя и фамилия', 82 | 'email': 'Email', 83 | 'subject':'Тема сообщения', 84 | 'body':'Текст сообщения' 85 | } 86 | 87 | def __init__(self, *args, **kwargs): 88 | super(MessageForm, self).__init__(*args, **kwargs) 89 | 90 | for name, field in self.fields.items(): 91 | field.widget.attrs.update({'class': 'form-control input-box form-ensurance-header-control'}) 92 | -------------------------------------------------------------------------------- /users/templates/users/message.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block content %} 3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |

15 | 16 |

17 |
{{ message.subject }}
18 |
19 |
20 | {% if message.sender %} 21 | {{ message.name }} 23 | {% else %} 24 | Отправитель: {{ message.name }} 25 | {% endif %} 26 | {{ message.created }} 27 |
28 |
29 |
30 |
31 |
{{ message.body|linebreaksbr }}
32 |
33 |
34 |
35 |
36 | 41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | {% endblock content %} 49 | -------------------------------------------------------------------------------- /users/templates/users/message_form.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block content %} 3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | {% csrf_token %} 14 |
15 | 19 | {% if request.user.is_authenticated == False %} 20 |
21 | 22 | {{ form.name }} 23 |
24 |
25 | 26 | {{ form.email }} 27 |
28 | {% endif %} 29 |
30 | 31 | {{ form.subject }} 32 |
33 |
34 | 35 | {{ form.body }} 36 |
37 |
38 | 39 |
40 | 41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | 50 | {% endblock content %} 51 | -------------------------------------------------------------------------------- /templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | {% load static %} 3 | 4 | 5 | 6 | 7 | 8 | Blog it! Платформа для увлеченных людей 9 | 12 | 14 | 15 | 16 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | {% include 'navbar.html' %} 25 | {% if messages %} 26 | {% for message in messages %} 27 | 36 | {% endfor %} 37 | {% endif %} 38 | {% block content %} 39 | {% endblock content %} 40 |
41 |
42 |
43 |
44 |
45 |

Все права принадлежат © Blog it! 2023

46 |
47 |
48 |

49 | Платформа работает на Django 52 |

53 |
54 |
55 |
56 |
57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /users/models.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import User 2 | from django.db import models 3 | from pytils.translit import slugify 4 | 5 | 6 | 7 | class Profile(models.Model): 8 | user = models.OneToOneField(User, 9 | on_delete=models.CASCADE) 10 | name = models.CharField(max_length=50, 11 | blank=True, null=True) 12 | email = models.EmailField(max_length=50, 13 | blank=True, null=True) 14 | username = models.CharField(max_length=50, 15 | blank=True, null=True) 16 | summary = models.CharField(max_length=100, 17 | blank=True, null=True) 18 | city = models.CharField(max_length=20, 19 | blank=True, null=True) 20 | profession = models.CharField(max_length=50, 21 | blank=True, null=True) 22 | about = models.TextField(blank=True, null=True) 23 | image = models.ImageField( 24 | null=True, blank=True, upload_to='profile_images', 25 | default='profile_images/default.jpg') 26 | github = models.CharField(max_length=100, 27 | default='https://github.com') 28 | twitter = models.CharField(max_length=100, 29 | default='https://twitter.com') 30 | linkedin = models.CharField(max_length=100, 31 | default='https://linkedin.com') 32 | youtube = models.CharField(max_length=100, 33 | default='https://youtube.com') 34 | website = models.CharField(max_length=100, 35 | default='https://mysite.com') 36 | created = models.DateTimeField(auto_now_add=True) 37 | follows = models.ManyToManyField( 38 | 'self', related_name='followed_by', 39 | symmetrical=False, blank=True 40 | ) 41 | 42 | class Meta: 43 | ordering = ['created'] 44 | verbose_name = 'Профиль' 45 | verbose_name_plural = 'Профили' 46 | 47 | def __str__(self): 48 | return self.user.username 49 | 50 | class Interest(models.Model): 51 | name = models.CharField(max_length=50, 52 | blank=True, null=True) 53 | slug = models.SlugField() 54 | description = models.TextField(null=True, blank=True) 55 | created = models.DateTimeField(auto_now_add=True) 56 | profile = models.ForeignKey( 57 | Profile, null=True, blank=True, 58 | on_delete=models.CASCADE) 59 | 60 | def save(self, *args, **kwargs): 61 | value = self.name 62 | self.slug = slugify(value) 63 | super().save(*args, **kwargs) 64 | 65 | class Meta: 66 | ordering = ['created'] 67 | verbose_name = 'Интерес' 68 | verbose_name_plural = 'Интересы' 69 | unique_together = ('name', 'slug', 'profile') 70 | 71 | def __str__(self): 72 | return self.name 73 | 74 | class Message(models.Model): 75 | sender = models.ForeignKey( 76 | Profile, on_delete=models.SET_NULL, 77 | null=True, blank=True) 78 | recipient = models.ForeignKey( 79 | Profile, on_delete=models.SET_NULL, 80 | null=True, blank=True, related_name='messages') 81 | name = models.CharField(max_length=200, 82 | null=True, blank=True) 83 | email = models.EmailField(max_length=200, 84 | null=True, blank=True) 85 | subject = models.CharField(max_length=200, 86 | null=True, blank=True) 87 | body = models.TextField() 88 | is_read = models.BooleanField(default=False, null=True) 89 | created = models.DateTimeField(auto_now_add=True) 90 | 91 | 92 | def __str__(self): 93 | return self.subject 94 | 95 | class Meta: 96 | ordering = ['is_read', '-created'] 97 | verbose_name = 'Сообщение' 98 | verbose_name_plural = 'Сообщения' -------------------------------------------------------------------------------- /blog/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.auth.models import User 3 | from users.models import Profile 4 | from pytils.translit import slugify 5 | 6 | class Category(models.Model): 7 | name = models.CharField('category', max_length=20) 8 | slug = models.SlugField() 9 | class Meta: 10 | ordering = ['id'] 11 | verbose_name = 'Категория' 12 | verbose_name_plural = 'Категории' 13 | def __str__(self): 14 | return self.name 15 | 16 | class Tag(models.Model): 17 | name = models.CharField('tag', max_length=20) 18 | slug = models.SlugField() 19 | 20 | def save(self, *args, **kwargs): 21 | value = self.name 22 | self.slug = slugify(value) 23 | super().save(*args, **kwargs) 24 | 25 | class Meta: 26 | ordering = ['id'] 27 | verbose_name = 'Тег' 28 | verbose_name_plural = 'Теги' 29 | 30 | def __str__(self): 31 | return self.name 32 | 33 | class Post(models.Model): 34 | owner = models.ForeignKey( 35 | Profile, null=True, blank=True, on_delete=models.CASCADE) 36 | title = models.CharField(max_length=150) 37 | slug = models.SlugField() 38 | text = models.TextField(max_length=2000) 39 | category = models.ForeignKey(Category, 40 | on_delete=models.CASCADE) 41 | tags = models.ManyToManyField(Tag, blank=True) 42 | published = models.DateTimeField(auto_now_add=True) 43 | likes = models.ManyToManyField(User, 44 | related_name='post_likes', blank=True) 45 | bookmarks = models.ManyToManyField(User, 46 | related_name='bookmarks', blank=True) 47 | 48 | def get_unique_slug(self): 49 | slug = slugify(self.title) 50 | unique_slug = slug 51 | num = 1 52 | while Post.objects.filter(slug=unique_slug).exists(): 53 | unique_slug = f"{slug}{num}" 54 | num += 1 55 | return unique_slug 56 | 57 | def save(self, *args, **kwargs): 58 | if not self.slug: 59 | self.slug = self.get_unique_slug() 60 | super().save(*args, **kwargs) 61 | 62 | class Meta: 63 | ordering = ['-published'] 64 | verbose_name = 'Запись' 65 | verbose_name_plural = 'Записи' 66 | 67 | def __str__(self): 68 | return f'{self.owner} {self.title}' 69 | 70 | def number_of_likes(self): 71 | if self.likes.count() == 0: 72 | return '' 73 | else: 74 | return self.likes.count() 75 | 76 | def number_of_bookmarks(self): 77 | if self.bookmarks.count() == 0: 78 | return '' 79 | else: 80 | return self.bookmarks.count() 81 | 82 | def number_of_comments(self): 83 | if self.comments.count() == 0: 84 | return '' 85 | else: 86 | return self.comments.filter(approved=True).count() 87 | 88 | 89 | class Comment(models.Model): 90 | post = models.ForeignKey(Post, 91 | on_delete=models.CASCADE, related_name='comments') 92 | owner = models.ForeignKey( 93 | Profile, null=True, blank=True, on_delete=models.CASCADE) 94 | text = models.TextField() 95 | published = models.DateTimeField(auto_now_add=True) 96 | approved = models.BooleanField(default=False) 97 | 98 | class Meta: 99 | ordering = ['-published'] 100 | verbose_name = 'Комментарий' 101 | verbose_name_plural = 'Комментарии' 102 | 103 | def approve(self): 104 | self.approved = True 105 | self.save() 106 | 107 | def __str__(self): 108 | return self.text -------------------------------------------------------------------------------- /config/settings.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | import os 3 | from django.contrib.messages import constants as messages 4 | from django.urls.base import reverse_lazy 5 | 6 | MESSAGE_TAGS = { 7 | messages.DEBUG: 'alert-secondary', 8 | messages.INFO: 'alert-info', 9 | messages.SUCCESS: 'alert-success', 10 | messages.WARNING: 'alert-warning', 11 | messages.ERROR: 'alert-danger', 12 | } 13 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 14 | BASE_DIR = Path(__file__).resolve().parent.parent 15 | 16 | 17 | SECRET_KEY = 'verysecretkey' 18 | 19 | # SECURITY WARNING: don't run with debug turned on in production! 20 | DEBUG = True 21 | 22 | ALLOWED_HOSTS = [] 23 | 24 | 25 | # Application definition 26 | 27 | INSTALLED_APPS = [ 28 | 'django.contrib.admin', 29 | 'django.contrib.auth', 30 | 'django.contrib.contenttypes', 31 | 'django.contrib.sessions', 32 | 'django.contrib.messages', 33 | 'django.contrib.staticfiles', 34 | 'polls.apps.PollsConfig', 35 | 'users.apps.UsersConfig', 36 | 'quizzes.apps.QuizzesConfig', 37 | 'blog.apps.BlogConfig', 38 | ] 39 | 40 | MIDDLEWARE = [ 41 | 'django.middleware.security.SecurityMiddleware', 42 | 'django.contrib.sessions.middleware.SessionMiddleware', 43 | 'django.middleware.common.CommonMiddleware', 44 | 'django.middleware.csrf.CsrfViewMiddleware', 45 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 46 | 'django.contrib.messages.middleware.MessageMiddleware', 47 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 48 | ] 49 | 50 | ROOT_URLCONF = 'config.urls' 51 | 52 | TEMPLATES = [ 53 | { 54 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 55 | 'DIRS': [os.path.join(BASE_DIR, 'templates')], 56 | 'APP_DIRS': True, 57 | 'OPTIONS': { 58 | 'context_processors': [ 59 | 'django.template.context_processors.debug', 60 | 'django.template.context_processors.request', 61 | 'django.contrib.auth.context_processors.auth', 62 | 'django.contrib.messages.context_processors.messages', 63 | ], 64 | }, 65 | }, 66 | ] 67 | 68 | WSGI_APPLICATION = 'config.wsgi.application' 69 | 70 | 71 | # Database 72 | # https://docs.djangoproject.com/en/4.1/ref/settings/#databases 73 | 74 | DATABASES = { 75 | 'default': { 76 | 'ENGINE': 'django.db.backends.sqlite3', 77 | 'NAME': BASE_DIR / 'db.sqlite3', 78 | } 79 | } 80 | 81 | 82 | # Password validation 83 | # https://docs.djangoproject.com/en/4.1/ref/settings/#auth-password-validators 84 | 85 | AUTH_PASSWORD_VALIDATORS = [ 86 | { 87 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 88 | }, 89 | { 90 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 91 | }, 92 | { 93 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 94 | }, 95 | { 96 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 97 | }, 98 | ] 99 | 100 | 101 | # Internationalization 102 | # https://docs.djangoproject.com/en/4.1/topics/i18n/ 103 | 104 | LANGUAGE_CODE = 'ru-Ru' 105 | 106 | TIME_ZONE = 'Europe/Moscow' 107 | 108 | USE_I18N = True 109 | 110 | USE_TZ = True 111 | 112 | LOGIN_URL = reverse_lazy('login') 113 | 114 | STATIC_URL = '/static/' 115 | 116 | STATIC_ROOT = os.path.join(BASE_DIR, 'static/') 117 | 118 | MEDIA_URL = '/media/' 119 | 120 | MEDIA_ROOT = os.path.join(BASE_DIR, 'media/') 121 | 122 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' 123 | -------------------------------------------------------------------------------- /blog/templates/blog/post_footer.html: -------------------------------------------------------------------------------- 1 | 81 | -------------------------------------------------------------------------------- /quizzes/templates/quizzes/quizzes.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block content %} 3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | profile 15 |
16 |
17 |
18 |
{{ profile.name }}
19 |
20 |
21 | 27 |
28 |
29 |
30 |
31 |
32 |
{% include 'socials.html' %}
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
Тесты
42 |
43 |
44 |
45 | {% for quiz in quizzes %} 46 |
47 |
48 | {{ quiz.published }} 49 |
{{ quiz.name }}
50 |
51 | Пройти 53 |
54 |
55 |
56 | {% endfor %} 57 |
58 |
{% include 'pagination.html' with queryset=quizzes custom_range=custom_range %}
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | {% endblock %} 68 | -------------------------------------------------------------------------------- /polls/templates/polls/questions.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block content %} 3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | profile 15 |
16 |
17 |
18 |
{{ profile.name }}
19 |
20 |
21 | 27 |
28 |
29 |
30 |
31 |
32 |
{% include 'socials.html' %}
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
Опросы и голосования
42 |
43 |
44 |
45 | {% for question in questions %} 46 |
47 |
48 | {{ question.published }} 49 |
{{ question.name }}
50 | 56 |
57 |
58 | {% endfor %} 59 |
60 |
{% include 'pagination.html' with queryset=questions custom_range=custom_range %}
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | {% endblock %} 70 | -------------------------------------------------------------------------------- /polls/templates/polls/question.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block content %} 3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | profile 15 |
16 |
17 |
18 |
{{ profile.name }}
19 |
20 |
21 | 27 |
28 |
29 |
30 |
31 |
32 |
{% include 'socials.html' %}
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | К опросам 43 |
{{ question.name }}
44 |
45 |
46 |
47 |
48 | {% csrf_token %} 49 | {% for choice in question.choice_set.all %} 50 |
51 | 56 | 57 |
58 | {% endfor %} 59 |
60 | 63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 | {% endblock %} 76 | -------------------------------------------------------------------------------- /quizzes/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import get_object_or_404, render, redirect 2 | from django.urls import reverse 3 | from users.utils import paginateObjects 4 | from django.contrib.auth.decorators import login_required 5 | from django.db.models import F 6 | from .models import Quiz, Question, Answer, Choice, Result 7 | 8 | @login_required 9 | def quizzes(request): 10 | profile = request.user.profile 11 | quizzes = Quiz.objects.all() 12 | custom_range, quizzes = paginateObjects(request, 13 | quizzes, 3) 14 | context = {'quizzes': quizzes, 'profile': profile, 15 | 'custom_range': custom_range} 16 | return render(request, 17 | 'quizzes/quizzes.html', context) 18 | 19 | @login_required 20 | def display_quiz(request, quiz_id): 21 | quiz = get_object_or_404(Quiz, pk=quiz_id) 22 | question = quiz.question_set.first() 23 | return redirect(reverse('quizzes:display_question', 24 | kwargs={'quiz_id': quiz_id, 25 | 'question_id': question.pk})) 26 | 27 | @login_required 28 | def display_question(request, quiz_id, question_id): 29 | profile = request.user.profile 30 | quiz = get_object_or_404(Quiz, pk=quiz_id) 31 | questions = quiz.question_set.all() 32 | current_question, next_question = None, None 33 | for ind, question in enumerate(questions): 34 | if question.pk == question_id: 35 | current_question = question 36 | if ind != len(questions) - 1: 37 | next_question = questions[ind + 1] 38 | context = {'quiz': quiz, 39 | 'question': current_question, 40 | 'next_question': next_question, 41 | 'profile': profile} 42 | return render(request, 43 | 'quizzes/display.html',context) 44 | 45 | @login_required 46 | def grade_question(request, quiz_id, question_id): 47 | question = get_object_or_404(Question, pk=question_id) 48 | quiz = get_object_or_404(Quiz, pk=quiz_id) 49 | can_answer = question.user_can_answer(request.user) 50 | try: 51 | if not can_answer: 52 | return render(request, 53 | 'quizzes/partial.html', 54 | {'question': question, 55 | 'error_message': 'Вы уже отвечали на этот вопрос.'}) 56 | 57 | if question.qtype == 'single': 58 | correct_answer = question.get_answers() 59 | user_answer = question.answer_set.get(pk=request.POST['answer']) 60 | choice = Choice(user=request.user, 61 | question=question, answer=user_answer) 62 | choice.save() 63 | is_correct = correct_answer == user_answer 64 | result, created = Result.objects.get_or_create(user=request.user, 65 | quiz=quiz) 66 | if is_correct is True: 67 | result.correct = F('correct') + 1 68 | else: 69 | result.wrong = F('wrong') + 1 70 | result.save() 71 | 72 | elif question.qtype == 'multiple': 73 | correct_answer = question.get_answers() 74 | answers_ids = request.POST.getlist('answer') 75 | user_answers = [] 76 | if answers_ids: 77 | for answer_id in answers_ids: 78 | user_answer = Answer.objects.get(pk=answer_id) 79 | user_answers.append(user_answer.name) 80 | choice = Choice(user=request.user, 81 | question=question, answer=user_answer) 82 | choice.save() 83 | is_correct = correct_answer == user_answers 84 | result, created = Result.objects.get_or_create(user=request.user, 85 | quiz=quiz) 86 | if is_correct is True: 87 | result.correct = F('correct') + 1 88 | else: 89 | result.wrong = F('wrong') + 1 90 | result.save() 91 | 92 | except: 93 | return render(request, 'quizzes/partial.html', 94 | {'question': question}) 95 | return render( 96 | request,'quizzes/partial.html', 97 | {'is_correct': is_correct, 98 | 'correct_answer': correct_answer, 99 | 'question': question}) 100 | 101 | @login_required 102 | def quiz_results(request, quiz_id): 103 | profile = request.user.profile 104 | quiz = get_object_or_404(Quiz, pk=quiz_id) 105 | questions = quiz.question_set.all() 106 | results = Result.objects.filter(user=request.user, 107 | quiz=quiz).values() 108 | correct = [i['correct'] for i in results][0] 109 | wrong = [i['wrong'] for i in results][0] 110 | context = {'quiz': quiz, 111 | 'profile': profile, 112 | 'correct': correct, 113 | 'wrong': wrong, 114 | 'number': len(questions), 115 | 'skipped': len(questions) - (correct + wrong)} 116 | return render(request, 117 | 'quizzes/results.html', context) 118 | 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /users/templates/users/profiles.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block content %} 3 |
4 |
5 |
6 |

Поиск по профилям

7 |
8 |
9 |
10 | 17 | 18 | 19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | {% for profile in profiles %} 28 |
29 |
30 |
31 |
32 |

{{ profile.profession }}

33 |
34 | {{ profile.city }} 35 |
36 |
37 |
{{ profile.name }}
38 |
39 |
40 | 41 | profile 42 | 43 |
44 |
45 |

{{ profile.summary }}

46 |
47 |
48 | {% csrf_token %} 49 | {% if profile in user.profile.follows.all %} 50 | 51 | 55 | {% else %} 56 | 60 | {% endif %} 61 | 62 |
63 | 64 | Сообщение 66 | 67 |
68 |
69 |
70 |
71 | {% endfor %} 72 |
73 |
74 |
75 | {% include 'pagination.html' with queryset=profiles custom_range=custom_range %} 76 |
77 |
78 | {% endblock content %} 79 | -------------------------------------------------------------------------------- /blog/templates/blog/my-blog.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block content %} 3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | profile 15 |
16 |
17 |
18 |
{{ profile.name }}
19 |
20 |
21 | 27 | {% include 'tags_categories.html' %} 28 |
29 |
30 |
31 |
32 |
33 |
{% include 'socials.html' %}
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
Мои записи
43 |
44 |
45 |
46 | {% for post in posts %} 47 |
48 |
49 |
50 | {{ post.category }} 51 | 52 |
53 |
54 |
55 |
{{ post.title }}
56 |

{{ post.text|safe|truncatewords:"50" }}

57 |
58 | Читать 60 |
61 |
62 | {% include 'blog/post_footer.html' %} 63 |
64 | {% endfor %} 65 |
66 |
{% include 'pagination.html' with queryset=posts custom_range=custom_range %}
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 | {% endblock content %} 76 | -------------------------------------------------------------------------------- /users/templates/users/inbox.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block content %} 3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | Новые сообщения: {{ unreadCount }} 16 |
17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | {% for message in messageRequests %} 30 | 31 | {% if message.is_read == False %} 32 | 36 | 37 | 38 | 39 | 40 | {% else %} 41 | 45 | 46 | 47 | 48 | 49 | {% endif %} 50 | 51 | {% endfor %} 52 | 53 |
СтатусНикнеймИмя отправителяТемаДата
33 | 35 | {{ message.sender }}{{ message.name }}{{ message.subject }}{{ message.created }} 42 | 44 | {{ message.sender }}{{ message.name }}{{ message.subject }}{{ message.created }}
54 |
55 | 60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 | {% endblock content %} 72 | -------------------------------------------------------------------------------- /polls/templates/polls/results.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block content %} 3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | profile 15 |
16 |
17 |
18 |
{{ profile.name }}
19 |
20 |
21 | 27 |
28 |
29 |
30 |
31 |
32 |
{% include 'socials.html' %}
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | К опросам 43 |
{{ question.name }}
44 |
45 |
46 |
47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | {% for choice in question.choice_set.all %} 56 | 57 | 58 | 59 | 60 | {% endfor %} 61 | 62 |
Вариант ответаКоличество голосов
{{ choice.name }}{{ choice.votes }}
63 |
64 |
65 |
66 | 67 |
68 |
69 | 70 | 86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 | {% endblock %} 95 | -------------------------------------------------------------------------------- /static/vendors/anchorjs/anchor.min.js: -------------------------------------------------------------------------------- 1 | // @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt Expat 2 | // 3 | // AnchorJS - v4.3.1 - 2021-04-17 4 | // https://www.bryanbraun.com/anchorjs/ 5 | // Copyright (c) 2021 Bryan Braun; Licensed MIT 6 | // 7 | // @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt Expat 8 | !function(A,e){"use strict";"function"==typeof define&&define.amd?define([],e):"object"==typeof module&&module.exports?module.exports=e():(A.AnchorJS=e(),A.anchors=new A.AnchorJS)}(this,function(){"use strict";return function(A){function d(A){A.icon=Object.prototype.hasOwnProperty.call(A,"icon")?A.icon:"",A.visible=Object.prototype.hasOwnProperty.call(A,"visible")?A.visible:"hover",A.placement=Object.prototype.hasOwnProperty.call(A,"placement")?A.placement:"right",A.ariaLabel=Object.prototype.hasOwnProperty.call(A,"ariaLabel")?A.ariaLabel:"Anchor",A.class=Object.prototype.hasOwnProperty.call(A,"class")?A.class:"",A.base=Object.prototype.hasOwnProperty.call(A,"base")?A.base:"",A.truncate=Object.prototype.hasOwnProperty.call(A,"truncate")?Math.floor(A.truncate):64,A.titleText=Object.prototype.hasOwnProperty.call(A,"titleText")?A.titleText:""}function w(A){var e;if("string"==typeof A||A instanceof String)e=[].slice.call(document.querySelectorAll(A));else{if(!(Array.isArray(A)||A instanceof NodeList))throw new TypeError("The selector provided to AnchorJS was invalid.");e=[].slice.call(A)}return e}this.options=A||{},this.elements=[],d(this.options),this.isTouchDevice=function(){return Boolean("ontouchstart"in window||window.TouchEvent||window.DocumentTouch&&document instanceof DocumentTouch)},this.add=function(A){var e,t,o,i,n,s,a,c,r,l,h,u,p=[];if(d(this.options),"touch"===(l=this.options.visible)&&(l=this.isTouchDevice()?"always":"hover"),0===(e=w(A=A||"h2, h3, h4, h5, h6")).length)return this;for(null===document.head.querySelector("style.anchorjs")&&((u=document.createElement("style")).className="anchorjs",u.appendChild(document.createTextNode("")),void 0===(A=document.head.querySelector('[rel="stylesheet"],style'))?document.head.appendChild(u):document.head.insertBefore(u,A),u.sheet.insertRule(".anchorjs-link{opacity:0;text-decoration:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}",u.sheet.cssRules.length),u.sheet.insertRule(":hover>.anchorjs-link,.anchorjs-link:focus{opacity:1}",u.sheet.cssRules.length),u.sheet.insertRule("[data-anchorjs-icon]::after{content:attr(data-anchorjs-icon)}",u.sheet.cssRules.length),u.sheet.insertRule('@font-face{font-family:anchorjs-icons;src:url(data:n/a;base64,AAEAAAALAIAAAwAwT1MvMg8yG2cAAAE4AAAAYGNtYXDp3gC3AAABpAAAAExnYXNwAAAAEAAAA9wAAAAIZ2x5ZlQCcfwAAAH4AAABCGhlYWQHFvHyAAAAvAAAADZoaGVhBnACFwAAAPQAAAAkaG10eASAADEAAAGYAAAADGxvY2EACACEAAAB8AAAAAhtYXhwAAYAVwAAARgAAAAgbmFtZQGOH9cAAAMAAAAAunBvc3QAAwAAAAADvAAAACAAAQAAAAEAAHzE2p9fDzz1AAkEAAAAAADRecUWAAAAANQA6R8AAAAAAoACwAAAAAgAAgAAAAAAAAABAAADwP/AAAACgAAA/9MCrQABAAAAAAAAAAAAAAAAAAAAAwABAAAAAwBVAAIAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAMCQAGQAAUAAAKZAswAAACPApkCzAAAAesAMwEJAAAAAAAAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAQAAg//0DwP/AAEADwABAAAAAAQAAAAAAAAAAAAAAIAAAAAAAAAIAAAACgAAxAAAAAwAAAAMAAAAcAAEAAwAAABwAAwABAAAAHAAEADAAAAAIAAgAAgAAACDpy//9//8AAAAg6cv//f///+EWNwADAAEAAAAAAAAAAAAAAAAACACEAAEAAAAAAAAAAAAAAAAxAAACAAQARAKAAsAAKwBUAAABIiYnJjQ3NzY2MzIWFxYUBwcGIicmNDc3NjQnJiYjIgYHBwYUFxYUBwYGIwciJicmNDc3NjIXFhQHBwYUFxYWMzI2Nzc2NCcmNDc2MhcWFAcHBgYjARQGDAUtLXoWOR8fORYtLTgKGwoKCjgaGg0gEhIgDXoaGgkJBQwHdR85Fi0tOAobCgoKOBoaDSASEiANehoaCQkKGwotLXoWOR8BMwUFLYEuehYXFxYugC44CQkKGwo4GkoaDQ0NDXoaShoKGwoFBe8XFi6ALjgJCQobCjgaShoNDQ0NehpKGgobCgoKLYEuehYXAAAADACWAAEAAAAAAAEACAAAAAEAAAAAAAIAAwAIAAEAAAAAAAMACAAAAAEAAAAAAAQACAAAAAEAAAAAAAUAAQALAAEAAAAAAAYACAAAAAMAAQQJAAEAEAAMAAMAAQQJAAIABgAcAAMAAQQJAAMAEAAMAAMAAQQJAAQAEAAMAAMAAQQJAAUAAgAiAAMAAQQJAAYAEAAMYW5jaG9yanM0MDBAAGEAbgBjAGgAbwByAGoAcwA0ADAAMABAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAH//wAP) format("truetype")}',u.sheet.cssRules.length)),u=document.querySelectorAll("[id]"),t=[].map.call(u,function(A){return A.id}),i=0;i\]./()*\\\n\t\b\v\u00A0]/g,"-").replace(/-{2,}/g,"-").substring(0,this.options.truncate).replace(/^-+|-+$/gm,"").toLowerCase()},this.hasAnchorJSLink=function(A){var e=A.firstChild&&-1<(" "+A.firstChild.className+" ").indexOf(" anchorjs-link "),A=A.lastChild&&-1<(" "+A.lastChild.className+" ").indexOf(" anchorjs-link ");return e||A||!1}}}); 9 | // @license-end -------------------------------------------------------------------------------- /quizzes/templates/quizzes/results.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block content %} 3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | profile 15 |
16 |
17 |
18 |
{{ profile.name }}
19 |
20 |
21 | 27 |
28 |
29 |
30 |
31 |
32 |
{% include 'socials.html' %}
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | К тестам 43 |
{{ question.name }}
44 |
45 |
46 |
47 |
Результаты пользователя {{ request.user.username }}
48 |
{{ quiz }}
49 |
50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 |
ПоказателиРезультаты
Всего вопросов в тесте{{ number }}
Правильные ответы{{ correct }}
Неправильные ответы{{ wrong }}
Пропущено вопросов{{ skipped }}
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 | {% endblock %} 87 | -------------------------------------------------------------------------------- /blog/templates/blog/post_form.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block content %} 3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | {% csrf_token %} 14 |
15 |

16 | 17 |

18 | 23 |
24 | 25 | {{ form.title }} 26 |
27 |
28 | 29 | 37 |
38 | 39 | {% for checkbox in form.tags %} 40 |
41 | 50 |
51 | {% endfor %} 52 |
53 |
54 | 55 | {{ form.text }} 56 |
57 |
58 | 59 | 63 | 64 |
65 |
66 | 67 |
68 | 69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | 78 | {% endblock content %} 79 | -------------------------------------------------------------------------------- /users/templates/users/login_register.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load static %} 3 | {% block content %} 4 | {% if page == 'register' %} 5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
16 | {% csrf_token %} 17 |
18 |
19 | Blogit! 20 |
21 |

Добро пожаловать на сайт

22 | {% for field in form %} 23 |
24 | 25 | {{ field }} 26 |
27 | {% endfor %} 28 | {% for error in field.errors %}

{{ error }}

{% endfor %} 29 |
30 | 31 |
32 |
33 |
34 |

35 | Есть аккаунт? Войдите на сайт 36 |

37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | {% else %} 48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | {% csrf_token %} 58 |
59 |
60 | Blogit! 61 |
62 |

Добро пожаловать на сайт

63 |
64 | 65 | 70 |
71 |
72 | 73 | 78 |
79 |
80 | 81 |
82 |
83 |
84 |

85 | Нет аккаунта? Зарегистрируйтесь 86 |

87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 | {% endif %} 98 | {% endblock content %} 99 | -------------------------------------------------------------------------------- /static/vendors/rellax/rellax.min.js: -------------------------------------------------------------------------------- 1 | (function(q,g){"function"===typeof define&&define.amd?define([],g):"object"===typeof module&&module.exports?module.exports=g():q.Rellax=g()})("undefined"!==typeof window?window:global,function(){var q=function(g,u){function C(){if(3===a.options.breakpoints.length&&Array.isArray(a.options.breakpoints)){var f=!0,c=!0,b;a.options.breakpoints.forEach(function(a){"number"!==typeof a&&(c=!1);null!==b&&a=f[0]&&n< 5 | f[1]?"sm":n>=f[1]&&n=d[c].max?d[c].max:e),a.options.horizontal&&!a.options.vertical&&(b=b>=d[c].max?d[c].max:b));null!=d[c].maxY&&(e=e>=d[c].maxY?d[c].maxY:e);null!=d[c].maxX&&(b=b>=d[c].maxX?d[c].maxX:b);a.elems[c].style[E]="translate3d("+(a.options.horizontal?b:"0")+"px,"+(a.options.vertical?e:"0")+"px,"+d[c].zindex+"px) "+d[c].transform}a.options.callback(f)}; 14 | a.destroy=function(){for(var f=0;f 4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | profile 15 |
16 |
17 |
18 |
{{ profile.name }}
19 | 27 |
28 |
29 |
30 |
{% include 'socials.html' %}
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
Профиль пользователя
40 |
41 |
42 |
43 |
44 |
Город
45 |
46 |
47 |

{{ profile.city }}

48 |
49 |
50 |
51 |
52 |
Профессия
53 |
54 |
55 |

{{ profile.profession }}

56 |
57 |
58 |
59 |
60 |
О себе
61 |
62 |
63 |

{{ profile.about }}

64 |
65 |
66 |
67 |
68 |
Интересы
69 |
70 | 74 |
75 | {% for interest in profile.interest_set.all %} 76 |
77 |
78 |
79 | {{ interest.name }} 81 |
82 |
83 |
84 |

{{ interest.description }}

85 |
86 |
87 | 88 | 89 |
90 |
91 | {% endfor %} 92 |
93 |
94 |
Друзья
95 |
96 |
97 |
98 |
{% include 'users/friends.html' %}
99 |
{% include 'pagination.html' with queryset=profiles custom_range=custom_range %}
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 | {% endblock %} 109 | -------------------------------------------------------------------------------- /blog/templates/blog/user-blog.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block content %} 3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | profile 15 |
16 |
17 |
18 |
{{ profile.name }}
19 |
20 |
21 | {% if request.user.profile.id != profile.id %} 22 |
23 | {% csrf_token %} 24 | {% if profile in user.profile.follows.all %} 25 | 31 | {% else %} 32 | 38 | {% endif %} 39 |
40 | Написать сообщение 42 | Профиль 44 | {% else %} 45 | 49 | {% endif %} 50 | {% include 'tags_categories.html' %} 51 |
52 |
53 |
54 |
55 |
56 |
{% include 'socials.html' %}
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
Блог пользователя {{ profile.username }}
66 |
67 |
68 |
69 | {% for post in posts %} 70 |
71 |
72 |
73 | {{ post.category }} 74 | {% if request.user.profile.id == profile.id %} 75 | 76 | {% endif %} 77 |
78 |
79 |
80 |
{{ post.title }}
81 |

{{ post.text|safe|truncatewords:"50" }}

82 |
83 | Читать 85 |
86 |
87 | {% include 'blog/post_footer.html' %} 88 |
89 | {% endfor %} 90 |
91 |
{% include 'pagination.html' with queryset=posts custom_range=custom_range %}
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 | {% endblock content %} 101 | -------------------------------------------------------------------------------- /blog/views.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.decorators import login_required 2 | from django.shortcuts import render, redirect, get_object_or_404 3 | from .models import Post, Tag, Category 4 | from django.contrib import messages 5 | from django.http import HttpResponseRedirect 6 | from users.models import Profile 7 | from .forms import PostForm, CommentForm 8 | from .utils import searchPosts 9 | from users.utils import paginateObjects 10 | 11 | @login_required 12 | def createPost(request): 13 | profile = request.user.profile 14 | form = PostForm() 15 | 16 | if request.method == 'POST': 17 | request.POST.getlist('tags') 18 | newtags = request.POST.get('newtags').replace(',', " ").split() 19 | form = PostForm(request.POST) 20 | 21 | if form.is_valid(): 22 | post = form.save() 23 | post.owner = profile 24 | post.save() 25 | 26 | for tag in newtags: 27 | tag, created = Tag.objects.get_or_create(name=tag) 28 | post.tags.add(tag) 29 | 30 | return redirect("blog:my-blog") 31 | 32 | context = {'form': form} 33 | return render(request, "blog/post_form.html", context) 34 | 35 | @login_required 36 | def userBlog(request, username): 37 | profile = Profile.objects.get(username=username) 38 | posts = profile.post_set.all() 39 | tags, categories = get_tags_categories(posts) 40 | custom_range, posts = paginateObjects(request, posts, 3) 41 | 42 | context = {'profile': profile, 'posts': posts, 43 | 'tags': tags, 'categories': categories, 44 | 'custom_range': custom_range} 45 | 46 | return render(request, "blog/user-blog.html", context) 47 | 48 | @login_required 49 | def post(request, post_slug): 50 | post = Post.objects.get(slug=post_slug) 51 | profile = request.user.profile 52 | comments = post.comments.filter(approved=True) 53 | form = CommentForm() 54 | 55 | if request.method == 'POST': 56 | form = CommentForm(request.POST) 57 | comment = form.save(commit=False) 58 | comment.post = post 59 | comment.owner = request.user.profile 60 | comment.save() 61 | messages.success(request, 'Ваш комментарий появится после проверки модератором') 62 | return redirect('blog:post', post_slug=post.slug) 63 | return render(request, 'blog/single-post.html', 64 | {'post': post, 'profile': profile, 65 | 'comments': comments, 'form': form}) 66 | 67 | def get_tags_categories(posts): 68 | categories = set() 69 | tags = set() 70 | for post in posts: 71 | categories.add(post.category) 72 | for tag in post.tags.all(): 73 | tags.add(tag) 74 | return tags, categories 75 | 76 | @login_required 77 | def myBlog(request): 78 | profile = request.user.profile 79 | posts = profile.post_set.all() 80 | tags, categories = get_tags_categories(posts) 81 | 82 | custom_range, posts = paginateObjects(request, posts, 3) 83 | context = {'profile': profile, 'posts': posts, 84 | 'custom_range': custom_range, 'tags': tags, 85 | 'categories': categories} 86 | return render(request, 'blog/my-blog.html', context) 87 | 88 | @login_required 89 | def updatePost(request, pk): 90 | profile = request.user.profile 91 | post = profile.post_set.get(id=pk) 92 | form = PostForm(instance=post) 93 | 94 | if request.method == 'POST': 95 | newtags = request.POST.get('newtags').replace(',', " ").split() 96 | 97 | form = PostForm(request.POST, request.FILES, instance=post) 98 | if form.is_valid(): 99 | post = form.save() 100 | for tag in newtags: 101 | tag, created = Tag.objects.get_or_create(name=tag) 102 | post.tags.add(tag) 103 | 104 | return redirect('blog:my-blog') 105 | 106 | context = {'form': form, 'post': post} 107 | return render(request, "blog/post_form.html", context) 108 | 109 | 110 | @login_required 111 | def deletePost(request, pk): 112 | profile = request.user.profile 113 | post = profile.post_set.get(id=pk) 114 | if request.method == 'POST': 115 | post.delete() 116 | return redirect('blog:my-blog') 117 | context = {'object': post} 118 | return render(request, 'delete_template.html', context) 119 | 120 | @login_required 121 | def like_post(request, post_id): 122 | if request.method == "POST": 123 | post = get_object_or_404(Post, pk=post_id) 124 | if not post.likes.filter(id=request.user.id).exists(): 125 | post.likes.add(request.user) 126 | post.save() 127 | return HttpResponseRedirect(request.META.get('HTTP_REFERER')) 128 | else: 129 | post.likes.remove(request.user) 130 | post.save() 131 | return HttpResponseRedirect(request.META.get('HTTP_REFERER')) 132 | 133 | @login_required 134 | def bookmark_post(request, post_id): 135 | if request.method == "POST": 136 | post = get_object_or_404(Post, pk=post_id) 137 | if not post.bookmarks.filter(id=request.user.id).exists(): 138 | post.bookmarks.add(request.user) 139 | post.save() 140 | return HttpResponseRedirect(request.META.get('HTTP_REFERER')) 141 | else: 142 | post.bookmarks.remove(request.user) 143 | post.save() 144 | return HttpResponseRedirect(request.META.get('HTTP_REFERER')) 145 | 146 | @login_required 147 | def friends(request): 148 | profile = request.user.profile 149 | posts = Post.objects.filter( 150 | owner__in=profile.follows.all() 151 | ) 152 | 153 | tags, categories = get_tags_categories(posts) 154 | posts, search_query = searchPosts(request) 155 | custom_range, posts = paginateObjects(request, posts, 3) 156 | context = {'profile': profile, 'posts': posts, 157 | 'search_query': search_query, 158 | 'custom_range': custom_range, 159 | 'tags': tags, 'categories': categories} 160 | return render(request, 'blog/post_list.html', context) 161 | 162 | @login_required 163 | def user_bookmarks(request): 164 | profile = request.user.profile 165 | user = request.user 166 | posts = Post.objects.filter(bookmarks__in=[user]) 167 | tags, categories = get_tags_categories(posts) 168 | 169 | custom_range, posts = paginateObjects(request, posts, 3) 170 | context = {'profile': profile, "posts": posts, 171 | 'custom_range': custom_range, 172 | 'tags': tags, 'categories': categories} 173 | 174 | return render(request, "blog/post_list.html", context) 175 | 176 | @login_required 177 | def posts_by_category(request, category_slug): 178 | profile = request.user.profile 179 | category = get_object_or_404(Category, slug=category_slug) 180 | posts = Post.objects.filter(category__slug__contains = category_slug) 181 | tags, categories = get_tags_categories(posts) 182 | 183 | custom_range, posts = paginateObjects(request, posts, 3) 184 | context = {'profile': profile, 'posts': posts, 185 | 'custom_range': custom_range, 'tags': tags, 186 | 'category': category, 'categories': categories} 187 | return render(request, "blog/post_list.html", context) 188 | 189 | @login_required 190 | def posts_by_tag(request, tag_slug): 191 | profile = request.user.profile 192 | tag = get_object_or_404(Tag, slug=tag_slug) 193 | posts = Post.objects.filter(tags__in=[tag]) 194 | tags, categories = get_tags_categories(posts) 195 | 196 | custom_range, posts = paginateObjects(request, posts, 3) 197 | context = {'profile': profile, 'posts': posts, 198 | 'custom_range': custom_range, 'tags': tags, 199 | 'tag': tag, 'categories': categories} 200 | return render(request, "blog/post_list.html", context) -------------------------------------------------------------------------------- /static/assets/js/theme.min.js: -------------------------------------------------------------------------------- 1 | "use strict";function ownKeys(t,e){var n,o=Object.keys(t);return Object.getOwnPropertySymbols&&(n=Object.getOwnPropertySymbols(t),e&&(n=n.filter(function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable})),o.push.apply(o,n)),o}function _objectSpread(t){for(var e=1;e=window.pageYOffset&&n>=window.pageXOffset&&t+a<=window.pageYOffset+window.innerHeight&&n+o<=window.pageXOffset+window.innerWidth,partial:twindow.pageYOffset&&n+o>window.pageXOffset}},breakpoints={xs:0,sm:576,md:768,lg:992,xl:1200,xxl:1540},getBreakpoint=function(e){var t,e=e&&e.classList.value;return t=e?breakpoints[e.split(" ").filter(function(e){return e.includes("navbar-expand-")}).pop().split("-").pop()]:t},setCookie=function(e,t,n){var o=new Date;o.setTime(o.getTime()+n),document.cookie="".concat(e,"=").concat(t,";expires=").concat(o.toUTCString())},getCookie=function(e){e=document.cookie.match("(^|;) ?".concat(e,"=([^;]*)(;|$)"));return e&&e[2]},settings={tinymce:{theme:"oxide"},chart:{borderColor:"rgba(255, 255, 255, 0.8)"}},newChart=function(e,t){e=e.getContext("2d");return new window.Chart(e,t)},getItemFromStore=function(t,n){var o=2e?(b.style.backgroundImage=n.scrollTop?i:"none",b.style.transition="none"):utils.hasClass(b.querySelector(l),u)||(b.classList.add(a),b.classList.add(s),b.style.backgroundImage=i),window.innerWidth<=e&&(b.style.transition=utils.hasClass(o,"show")?c:"none")}),o.addEventListener(g,function(){b.classList.add(a),b.classList.add(s),b.style.backgroundImage=i,b.style.transition=c}),o.addEventListener(p,function(){b.classList.remove(a),b.classList.remove(s),n.scrollTop||(b.style.backgroundImage="none")}),o.addEventListener(h,function(){b.style.transition="none"}))};docReady(navbarInit),docReady(detectorInit); 2 | //# sourceMappingURL=theme.min.js.map 3 | -------------------------------------------------------------------------------- /quizzes/templates/quizzes/display.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block content %} 3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | profile 15 |
16 |
17 |
18 |
{{ profile.name }}
19 |
20 |
21 | 27 |
28 |
29 |
30 |
31 |
32 |
{% include 'socials.html' %}
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | К тестам 43 |
{{ question.name }}
44 |
45 |
46 |
47 |
50 | {% csrf_token %} 51 | {% if question.qtype == 'single' %} 52 |
Только один ответ здесь - правильный.
53 | {% for answer in question.answer_set.all %} 54 |
55 | 60 | 61 |
62 | {% endfor %} 63 | {% else %} 64 |
Здесь несколько правильных ответов.
65 | {% for answer in question.answer_set.all %} 66 |
67 | 72 | 73 |
74 | {% endfor %} 75 | {% endif %} 76 |
77 | 80 |
81 |
82 |
83 |
84 |
85 | {% if next_question %} 86 | 90 | {% else %} 91 |
92 | Это последний вопрос теста. После ответа нажмите кнопку 93 | Посмотреть результаты 95 |
96 | {% endif %} 97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 | 121 | {% endblock %} 122 | -------------------------------------------------------------------------------- /blog/templates/blog/single-post.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block content %} 3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | profile 15 |
16 |
17 |
18 |
{{ post.owner.name }}
19 |
20 |
21 | {% if request.user.profile.id != profile.id %} 22 | Новая запись 24 | {% else %} 25 | Блог пользователя 27 | {% endif %} 28 |
29 |
30 |
31 |
32 |
33 |
{% include 'socials.html' %}
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | {{ post.category }} 48 | {% if request.user.profile.id == post.owner.id %} 49 | 50 | {% else %} 51 | {{ post.published }} 52 | {% endif %} 53 |
54 |
55 |
56 |
{{ post.title }}
57 |

{{ post.text|safe }}

58 |
59 |
60 | {% include 'blog/post_footer.html' %} 61 |
62 |
63 |
64 | {% if request.user.is_authenticated %} 65 |
66 |
67 |
68 | {% csrf_token %} 69 | {% for field in form %} 70 |
71 | 72 | {{ field }} 73 |
74 | {% endfor %} 75 |
76 | 77 |
78 |
79 |
80 |
81 | {% endif %} 82 |
83 |
84 | {% for comment in comments %} 85 |
86 |
87 |
88 |
{{ comment.owner.name }}
89 |
{{ comment.published }}
90 |
91 |
92 |
93 |
94 |
95 | 96 | profile 97 | 98 |
99 |
100 |
101 |
102 |

{{ comment.text }}

103 |
104 |
105 |
106 |
107 | {% endfor %} 108 |
{% include 'pagination.html' with queryset=posts custom_range=custom_range %}
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 | {% endblock content %} 118 | -------------------------------------------------------------------------------- /users/templates/users/user-profile.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block content %} 3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | profile 15 |
16 |
17 |
18 |
{{ profile.name }}
19 |
20 |
21 | {% if request.user.profile.id != profile.id %} 22 |
23 | {% csrf_token %} 24 | {% if profile in user.profile.follows.all %} 25 | 31 | {% else %} 32 | 38 | {% endif %} 39 |
40 | Написать сообщение 42 | Блог пользователя 44 | {% else %} 45 | 49 | {% endif %} 50 |
51 |
52 |
53 |
54 |
55 |
{% include 'socials.html' %}
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
Профиль пользователя
65 |
66 |
67 |
68 |
69 |
Город
70 |
71 |
72 |

{{ profile.city }}

73 |
74 |
75 |
76 |
77 |
Профессия
78 |
79 |
80 |

{{ profile.profession }}

81 |
82 |
83 |
84 |
85 |
О себе
86 |
87 |
88 |

{{ profile.about }}

89 |
90 |
91 |
92 |
93 |
Интересы
94 |
95 |
96 |
97 | {% for interest in profile.interest_set.all %} 98 |
99 |
100 |
101 | {{ interest.name }} 103 |
104 |
105 |
106 |

{{ interest.description }}

107 |
108 |
109 |
110 | {% endfor %} 111 |
112 |
113 |
Друзья
114 |
115 |
116 |
117 |
{% include 'users/friends.html' %}
118 |
{% include 'pagination.html' with queryset=profiles custom_range=custom_range %}
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 | {% endblock content %} 128 | -------------------------------------------------------------------------------- /static/vendors/gsap/scrollToPlugin.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : 3 | typeof define === 'function' && define.amd ? define(['exports'], factory) : 4 | (global = global || self, factory(global.window = global.window || {})); 5 | }(this, (function (exports) { 'use strict'; 6 | 7 | /*! 8 | * ScrollToPlugin 3.7.1 9 | * https://greensock.com 10 | * 11 | * @license Copyright 2008-2021, GreenSock. All rights reserved. 12 | * Subject to the terms at https://greensock.com/standard-license or for 13 | * Club GreenSock members, the agreement issued with that membership. 14 | * @author: Jack Doyle, jack@greensock.com 15 | */ 16 | var gsap, 17 | _coreInitted, 18 | _window, 19 | _docEl, 20 | _body, 21 | _toArray, 22 | _config, 23 | _windowExists = function _windowExists() { 24 | return typeof window !== "undefined"; 25 | }, 26 | _getGSAP = function _getGSAP() { 27 | return gsap || _windowExists() && (gsap = window.gsap) && gsap.registerPlugin && gsap; 28 | }, 29 | _isString = function _isString(value) { 30 | return typeof value === "string"; 31 | }, 32 | _isFunction = function _isFunction(value) { 33 | return typeof value === "function"; 34 | }, 35 | _max = function _max(element, axis) { 36 | var dim = axis === "x" ? "Width" : "Height", 37 | scroll = "scroll" + dim, 38 | client = "client" + dim; 39 | return element === _window || element === _docEl || element === _body ? Math.max(_docEl[scroll], _body[scroll]) - (_window["inner" + dim] || _docEl[client] || _body[client]) : element[scroll] - element["offset" + dim]; 40 | }, 41 | _buildGetter = function _buildGetter(e, axis) { 42 | var p = "scroll" + (axis === "x" ? "Left" : "Top"); 43 | 44 | if (e === _window) { 45 | if (e.pageXOffset != null) { 46 | p = "page" + axis.toUpperCase() + "Offset"; 47 | } else { 48 | e = _docEl[p] != null ? _docEl : _body; 49 | } 50 | } 51 | 52 | return function () { 53 | return e[p]; 54 | }; 55 | }, 56 | _clean = function _clean(value, index, target, targets) { 57 | _isFunction(value) && (value = value(index, target, targets)); 58 | 59 | if (typeof value !== "object") { 60 | return _isString(value) && value !== "max" && value.charAt(1) !== "=" ? { 61 | x: value, 62 | y: value 63 | } : { 64 | y: value 65 | }; 66 | } else if (value.nodeType) { 67 | return { 68 | y: value, 69 | x: value 70 | }; 71 | } else { 72 | var result = {}, 73 | p; 74 | 75 | for (p in value) { 76 | result[p] = p !== "onAutoKill" && _isFunction(value[p]) ? value[p](index, target, targets) : value[p]; 77 | } 78 | 79 | return result; 80 | } 81 | }, 82 | _getOffset = function _getOffset(element, container) { 83 | element = _toArray(element)[0]; 84 | 85 | if (!element || !element.getBoundingClientRect) { 86 | return console.warn("scrollTo target doesn't exist. Using 0") || { 87 | x: 0, 88 | y: 0 89 | }; 90 | } 91 | 92 | var rect = element.getBoundingClientRect(), 93 | isRoot = !container || container === _window || container === _body, 94 | cRect = isRoot ? { 95 | top: _docEl.clientTop - (_window.pageYOffset || _docEl.scrollTop || _body.scrollTop || 0), 96 | left: _docEl.clientLeft - (_window.pageXOffset || _docEl.scrollLeft || _body.scrollLeft || 0) 97 | } : container.getBoundingClientRect(), 98 | offsets = { 99 | x: rect.left - cRect.left, 100 | y: rect.top - cRect.top 101 | }; 102 | 103 | if (!isRoot && container) { 104 | offsets.x += _buildGetter(container, "x")(); 105 | offsets.y += _buildGetter(container, "y")(); 106 | } 107 | 108 | return offsets; 109 | }, 110 | _parseVal = function _parseVal(value, target, axis, currentVal, offset) { 111 | return !isNaN(value) && typeof value !== "object" ? parseFloat(value) - offset : _isString(value) && value.charAt(1) === "=" ? parseFloat(value.substr(2)) * (value.charAt(0) === "-" ? -1 : 1) + currentVal - offset : value === "max" ? _max(target, axis) - offset : Math.min(_max(target, axis), _getOffset(value, target)[axis] - offset); 112 | }, 113 | _initCore = function _initCore() { 114 | gsap = _getGSAP(); 115 | 116 | if (_windowExists() && gsap && document.body) { 117 | _window = window; 118 | _body = document.body; 119 | _docEl = document.documentElement; 120 | _toArray = gsap.utils.toArray; 121 | gsap.config({ 122 | autoKillThreshold: 7 123 | }); 124 | _config = gsap.config(); 125 | _coreInitted = 1; 126 | } 127 | }; 128 | 129 | var ScrollToPlugin = { 130 | version: "3.7.1", 131 | name: "scrollTo", 132 | rawVars: 1, 133 | register: function register(core) { 134 | gsap = core; 135 | 136 | _initCore(); 137 | }, 138 | init: function init(target, value, tween, index, targets) { 139 | _coreInitted || _initCore(); 140 | var data = this, 141 | snapType = gsap.getProperty(target, "scrollSnapType"); 142 | data.isWin = target === _window; 143 | data.target = target; 144 | data.tween = tween; 145 | value = _clean(value, index, target, targets); 146 | data.vars = value; 147 | data.autoKill = !!value.autoKill; 148 | data.getX = _buildGetter(target, "x"); 149 | data.getY = _buildGetter(target, "y"); 150 | data.x = data.xPrev = data.getX(); 151 | data.y = data.yPrev = data.getY(); 152 | 153 | if (snapType && snapType !== "none") { 154 | data.snap = 1; 155 | data.snapInline = target.style.scrollSnapType; 156 | target.style.scrollSnapType = "none"; 157 | } 158 | 159 | if (value.x != null) { 160 | data.add(data, "x", data.x, _parseVal(value.x, target, "x", data.x, value.offsetX || 0), index, targets); 161 | 162 | data._props.push("scrollTo_x"); 163 | } else { 164 | data.skipX = 1; 165 | } 166 | 167 | if (value.y != null) { 168 | data.add(data, "y", data.y, _parseVal(value.y, target, "y", data.y, value.offsetY || 0), index, targets); 169 | 170 | data._props.push("scrollTo_y"); 171 | } else { 172 | data.skipY = 1; 173 | } 174 | }, 175 | render: function render(ratio, data) { 176 | var pt = data._pt, 177 | target = data.target, 178 | tween = data.tween, 179 | autoKill = data.autoKill, 180 | xPrev = data.xPrev, 181 | yPrev = data.yPrev, 182 | isWin = data.isWin, 183 | snap = data.snap, 184 | snapInline = data.snapInline, 185 | x, 186 | y, 187 | yDif, 188 | xDif, 189 | threshold; 190 | 191 | while (pt) { 192 | pt.r(ratio, pt.d); 193 | pt = pt._next; 194 | } 195 | 196 | x = isWin || !data.skipX ? data.getX() : xPrev; 197 | y = isWin || !data.skipY ? data.getY() : yPrev; 198 | yDif = y - yPrev; 199 | xDif = x - xPrev; 200 | threshold = _config.autoKillThreshold; 201 | 202 | if (data.x < 0) { 203 | data.x = 0; 204 | } 205 | 206 | if (data.y < 0) { 207 | data.y = 0; 208 | } 209 | 210 | if (autoKill) { 211 | if (!data.skipX && (xDif > threshold || xDif < -threshold) && x < _max(target, "x")) { 212 | data.skipX = 1; 213 | } 214 | 215 | if (!data.skipY && (yDif > threshold || yDif < -threshold) && y < _max(target, "y")) { 216 | data.skipY = 1; 217 | } 218 | 219 | if (data.skipX && data.skipY) { 220 | tween.kill(); 221 | data.vars.onAutoKill && data.vars.onAutoKill.apply(tween, data.vars.onAutoKillParams || []); 222 | } 223 | } 224 | 225 | if (isWin) { 226 | _window.scrollTo(!data.skipX ? data.x : x, !data.skipY ? data.y : y); 227 | } else { 228 | data.skipY || (target.scrollTop = data.y); 229 | data.skipX || (target.scrollLeft = data.x); 230 | } 231 | 232 | if (snap && (ratio === 1 || ratio === 0)) { 233 | y = target.scrollTop; 234 | x = target.scrollLeft; 235 | snapInline ? target.style.scrollSnapType = snapInline : target.style.removeProperty("scroll-snap-type"); 236 | target.scrollTop = y + 1; 237 | target.scrollLeft = x + 1; 238 | target.scrollTop = y; 239 | target.scrollLeft = x; 240 | } 241 | 242 | data.xPrev = data.x; 243 | data.yPrev = data.y; 244 | }, 245 | kill: function kill(property) { 246 | var both = property === "scrollTo"; 247 | 248 | if (both || property === "scrollTo_x") { 249 | this.skipX = 1; 250 | } 251 | 252 | if (both || property === "scrollTo_y") { 253 | this.skipY = 1; 254 | } 255 | } 256 | }; 257 | ScrollToPlugin.max = _max; 258 | ScrollToPlugin.getOffset = _getOffset; 259 | ScrollToPlugin.buildGetter = _buildGetter; 260 | _getGSAP() && gsap.registerPlugin(ScrollToPlugin); 261 | 262 | exports.ScrollToPlugin = ScrollToPlugin; 263 | exports.default = ScrollToPlugin; 264 | 265 | Object.defineProperty(exports, '__esModule', { value: true }); 266 | 267 | }))); 268 | -------------------------------------------------------------------------------- /blog/templates/blog/post_list.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block content %} 3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | profile 15 |
16 |
17 |
18 |
{{ profile.name }}
19 |
20 |
21 | 29 | {% include 'tags_categories.html' %} 30 |
31 |
32 |
33 |
34 |
35 |
{% include 'socials.html' %}
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | {% if 'friends' in request.path %} 45 |
Записи друзей
46 |
47 |
48 |
49 |
50 | 57 | 58 | 59 |
60 |
61 | {% elif 'bookmarks' in request.path %} 62 |
Избранное
63 | {% elif 'category' in request.path %} 64 |
65 | Все записи в категории {{ category.name }} 66 |
67 | {% elif 'tag' in request.path %} 68 |
69 | Все записи, отмеченные тегом {{ tag.name }} 70 |
71 | {% endif %} 72 |
73 |
74 |
75 | {% for post in posts %} 76 |
77 |
78 |
79 | {{ post.category }} 80 | {% if request.user.profile.id == post.owner.id %} 81 | 82 | {% else %} 83 | {{ post.published }} 84 | {% endif %} 85 |
86 |
87 |
88 |
{{ post.title }}
89 |
90 |
91 |
92 |
93 | profile 94 |
95 |
96 |

97 | {{ post.owner.name }} 99 |

100 |
101 |
102 |

{{ post.text|safe|truncatewords:"50" }}

103 |
104 |
105 |
106 | Читать 108 |
109 |
110 | {% include 'blog/post_footer.html' %} 111 |
112 | {% endfor %} 113 |
114 |
{% include 'pagination.html' with queryset=posts custom_range=custom_range %}
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 | {% endblock content %} 124 | --------------------------------------------------------------------------------