├── .gitignore ├── LICENSE ├── README.md ├── blog ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-312.pyc │ ├── admin.cpython-312.pyc │ ├── apps.cpython-312.pyc │ ├── models.cpython-312.pyc │ ├── urls.cpython-312.pyc │ └── views.cpython-312.pyc ├── admin.py ├── apps.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_initial.py │ ├── 0003_post_views.py │ ├── 0004_delete_category_delete_tag_remove_post_slug.py │ ├── __init__.py │ └── __pycache__ │ │ ├── 0001_initial.cpython-312.pyc │ │ ├── 0002_initial.cpython-312.pyc │ │ ├── 0003_post_views.cpython-312.pyc │ │ ├── 0004_delete_category_delete_tag_remove_post_slug.cpython-312.pyc │ │ └── __init__.cpython-312.pyc ├── models.py ├── tests.py ├── urls.py └── views.py ├── config ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-312.pyc │ ├── settings.cpython-312.pyc │ ├── urls.cpython-312.pyc │ └── wsgi.cpython-312.pyc ├── asgi.py ├── settings.py ├── urls.py └── wsgi.py ├── db.sqlite3 ├── manage.py ├── media └── default_profile_picture.jpg ├── requirements.txt ├── static ├── css │ ├── message.css │ ├── navbar.css │ ├── post_confirm_delete.css │ ├── post_detail.css │ ├── post_form.css │ ├── post_list.css │ ├── profile.css │ ├── registrations │ │ └── style.css │ └── settings.css └── js │ └── message.js ├── template ├── base.html ├── post_confirm_delete.html ├── post_detail.html ├── post_form.html ├── post_list.html ├── profile.html ├── registrations │ ├── signin.html │ └── signup.html └── settings.html └── user ├── __init__.py ├── __pycache__ ├── __init__.cpython-312.pyc ├── admin.cpython-312.pyc ├── apps.cpython-312.pyc ├── models.cpython-312.pyc ├── urls.cpython-312.pyc └── views.cpython-312.pyc ├── admin.py ├── apps.py ├── migrations ├── 0001_initial.py ├── __init__.py └── __pycache__ │ ├── 0001_initial.cpython-312.pyc │ └── __init__.cpython-312.pyc ├── models.py ├── tests.py ├── urls.py └── views.py /.gitignore: -------------------------------------------------------------------------------- 1 | # General 2 | .DS_Store 3 | .AppleDouble 4 | .LSOverride 5 | 6 | # Icon must end with two \r 7 | Icon 8 | 9 | # Thumbnails 10 | ._* 11 | 12 | # Files that might appear in the root of a volume 13 | .DocumentRevisions-V100 14 | .fseventsd 15 | .Spotlight-V100 16 | .TemporaryItems 17 | .Trashes 18 | .VolumeIcon.icns 19 | .com.apple.timemachine.donotpresent 20 | 21 | # Directories potentially created on remote AFP share 22 | .AppleDB 23 | .AppleDesktop 24 | Network Trash Folder 25 | Temporary Items 26 | .apdisk 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | ƒ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Django Blog Platform 2 | 3 | The Django Blog Platform is a comprehensive web application designed for blogging purposes, built with Django framework. It empowers users with a range of functionalities including user authentication, profile management, and content creation. 4 | 5 | ## Live Demo 6 | 7 | You can access a live demo of the Django Blog Platform by visiting [Demo Link](https://djangoblogplatform.pythonanywhere.com). Please note that this demo is for demonstration purposes only and may not reflect the latest changes or updates to the application. 8 | 9 | Feel free to replace `[https://djangoblogplatform.pythonanywhere.com]` with the actual link to your live demo. If you don't have a live demo yet, you can host your Django application on platforms like Heroku or PythonAnywhere and provide the URL accordingly. 10 | 11 | Make sure to keep the demo updated with the latest changes to your application for users to explore its functionalities effectively. 12 | 13 | ## Key Features 14 | 15 | - **User Authentication**: Seamlessly register, login, and logout with secure authentication mechanisms. 16 | - **Password Strength Validation**: Ensures password security by validating strength during registration. 17 | - **User Profile Management**: View and update user profiles with personalized information. 18 | - **Post Management**: Perform CRUD operations on posts including creation, reading, updating, and deletion. 19 | - **Interactive Interface**: User-friendly error and success messages enhance user experience. 20 | 21 | > [!TIP] 22 | > Access the administrator profile with custom settings using the credentials: username `ogabekavazov` and password `123`. 23 | 24 | ## Requirements 25 | 26 | - Python 3.x 27 | - Django 28 | - Pillow 29 | 30 | ## Setup 31 | 32 | 1. **Clone the Repository:** 33 | 34 | ```bash 35 | git clone https://github.com/DevOgabek/DjangoBlogPlatform.git 36 | ``` 37 | 38 | 2. **Install Dependencies:** 39 | 40 | ```bash 41 | pip install -r requirements.txt 42 | ``` 43 | 44 | 3. **Apply Migrations:** 45 | 46 | ```bash 47 | python manage.py migrate 48 | ``` 49 | 50 | 4. **Create Superuser:** 51 | 52 | ```bash 53 | python manage.py createsuperuser 54 | ``` 55 | 56 | 5. **Run the Server:** 57 | 58 | ```bash 59 | python manage.py runserver 60 | ``` 61 | 62 | 6. **Access the Application:** 63 | 64 | Visit `http://127.0.0.1:8000/` in your browser. 65 | 66 | ## Usage 67 | 68 | 1. **Sign Up:** Register by providing necessary details including username, email, first name, last name, and a strong password. 69 | 2. **Sign In:** Log in with your username and password. 70 | 3. **Create Post:** After logging in, create a new post by providing a title and content. 71 | 4. **View Post:** Click on a post title to read its content. 72 | 5. **Update Post:** Edit the title and content of your posts if you are the author. 73 | 6. **Delete Post:** Remove your posts if you are the author. 74 | 7. **View Profile:** Access your profile by clicking on your username. Update your personal information and bio. 75 | 8. **Sign Out:** Click on "Sign Out" to securely log out of your account. 76 | 77 | ## Credits 78 | 79 | This application is developed by O'gabek. 80 | 81 | ## License 82 | 83 | The project is licensed under the [MIT License](LICENSE). -------------------------------------------------------------------------------- /blog/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevOgabek/DjangoBlogPlatform/8dc40b39a6c0e2b44e29f88dd067a12505ac5bbb/blog/__init__.py -------------------------------------------------------------------------------- /blog/__pycache__/__init__.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevOgabek/DjangoBlogPlatform/8dc40b39a6c0e2b44e29f88dd067a12505ac5bbb/blog/__pycache__/__init__.cpython-312.pyc -------------------------------------------------------------------------------- /blog/__pycache__/admin.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevOgabek/DjangoBlogPlatform/8dc40b39a6c0e2b44e29f88dd067a12505ac5bbb/blog/__pycache__/admin.cpython-312.pyc -------------------------------------------------------------------------------- /blog/__pycache__/apps.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevOgabek/DjangoBlogPlatform/8dc40b39a6c0e2b44e29f88dd067a12505ac5bbb/blog/__pycache__/apps.cpython-312.pyc -------------------------------------------------------------------------------- /blog/__pycache__/models.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevOgabek/DjangoBlogPlatform/8dc40b39a6c0e2b44e29f88dd067a12505ac5bbb/blog/__pycache__/models.cpython-312.pyc -------------------------------------------------------------------------------- /blog/__pycache__/urls.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevOgabek/DjangoBlogPlatform/8dc40b39a6c0e2b44e29f88dd067a12505ac5bbb/blog/__pycache__/urls.cpython-312.pyc -------------------------------------------------------------------------------- /blog/__pycache__/views.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevOgabek/DjangoBlogPlatform/8dc40b39a6c0e2b44e29f88dd067a12505ac5bbb/blog/__pycache__/views.cpython-312.pyc -------------------------------------------------------------------------------- /blog/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Post 3 | 4 | admin.site.register(Post) -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /blog/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0.2 on 2024-02-23 06:04 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='Category', 16 | fields=[ 17 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('name', models.CharField(max_length=100)), 19 | ('slug', models.SlugField(unique=True)), 20 | ], 21 | ), 22 | migrations.CreateModel( 23 | name='Post', 24 | fields=[ 25 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 26 | ('title', models.CharField(max_length=200)), 27 | ('slug', models.SlugField(unique=True)), 28 | ('content', models.TextField()), 29 | ('created_at', models.DateTimeField(auto_now_add=True)), 30 | ('updated_at', models.DateTimeField(auto_now=True)), 31 | ], 32 | ), 33 | migrations.CreateModel( 34 | name='Tag', 35 | fields=[ 36 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 37 | ('name', models.CharField(max_length=100)), 38 | ('slug', models.SlugField(unique=True)), 39 | ], 40 | ), 41 | ] 42 | -------------------------------------------------------------------------------- /blog/migrations/0002_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0.2 on 2024-02-23 06:04 2 | 3 | import django.db.models.deletion 4 | from django.conf import settings 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | ('blog', '0001_initial'), 14 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 15 | ] 16 | 17 | operations = [ 18 | migrations.AddField( 19 | model_name='post', 20 | name='author', 21 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /blog/migrations/0003_post_views.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0.2 on 2024-02-23 06:54 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('blog', '0002_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='post', 15 | name='views', 16 | field=models.IntegerField(default=0), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /blog/migrations/0004_delete_category_delete_tag_remove_post_slug.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0.2 on 2024-02-23 13:44 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('blog', '0003_post_views'), 10 | ] 11 | 12 | operations = [ 13 | migrations.DeleteModel( 14 | name='Category', 15 | ), 16 | migrations.DeleteModel( 17 | name='Tag', 18 | ), 19 | migrations.RemoveField( 20 | model_name='post', 21 | name='slug', 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /blog/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevOgabek/DjangoBlogPlatform/8dc40b39a6c0e2b44e29f88dd067a12505ac5bbb/blog/migrations/__init__.py -------------------------------------------------------------------------------- /blog/migrations/__pycache__/0001_initial.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevOgabek/DjangoBlogPlatform/8dc40b39a6c0e2b44e29f88dd067a12505ac5bbb/blog/migrations/__pycache__/0001_initial.cpython-312.pyc -------------------------------------------------------------------------------- /blog/migrations/__pycache__/0002_initial.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevOgabek/DjangoBlogPlatform/8dc40b39a6c0e2b44e29f88dd067a12505ac5bbb/blog/migrations/__pycache__/0002_initial.cpython-312.pyc -------------------------------------------------------------------------------- /blog/migrations/__pycache__/0003_post_views.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevOgabek/DjangoBlogPlatform/8dc40b39a6c0e2b44e29f88dd067a12505ac5bbb/blog/migrations/__pycache__/0003_post_views.cpython-312.pyc -------------------------------------------------------------------------------- /blog/migrations/__pycache__/0004_delete_category_delete_tag_remove_post_slug.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevOgabek/DjangoBlogPlatform/8dc40b39a6c0e2b44e29f88dd067a12505ac5bbb/blog/migrations/__pycache__/0004_delete_category_delete_tag_remove_post_slug.cpython-312.pyc -------------------------------------------------------------------------------- /blog/migrations/__pycache__/__init__.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevOgabek/DjangoBlogPlatform/8dc40b39a6c0e2b44e29f88dd067a12505ac5bbb/blog/migrations/__pycache__/__init__.cpython-312.pyc -------------------------------------------------------------------------------- /blog/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.conf import settings 3 | 4 | class Post(models.Model): 5 | title = models.CharField(max_length=200) 6 | author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) 7 | content = models.TextField() 8 | created_at = models.DateTimeField(auto_now_add=True) 9 | updated_at = models.DateTimeField(auto_now=True) 10 | views = models.IntegerField(default=0) -------------------------------------------------------------------------------- /blog/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /blog/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from .views import ( 3 | post_list, 4 | post_detail, 5 | post_create, 6 | post_update, 7 | post_delete, 8 | ) 9 | 10 | urlpatterns = [ 11 | path('', post_list, name='post_list'), 12 | path('post/create/', post_create, name='post_create'), 13 | path('post//', post_detail, name='post_detail'), 14 | path('post//update/', post_update, name='post_update'), 15 | path('post//delete/', post_delete, name='post_delete'), 16 | ] 17 | -------------------------------------------------------------------------------- /blog/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render, redirect, get_object_or_404 2 | from django.contrib.auth.decorators import login_required 3 | from django.contrib import messages 4 | from .models import Post 5 | 6 | def post_list(request): 7 | posts = Post.objects.all() 8 | return render(request, 'post_list.html', {'posts': posts}) 9 | 10 | def post_detail(request, pk): 11 | post = get_object_or_404(Post, pk=pk) 12 | if request.user.is_authenticated: 13 | post.views += 1 14 | post.save() 15 | return render(request, 'post_detail.html', {'post': post}) 16 | 17 | @login_required(login_url='signin') 18 | def post_create(request): 19 | if request.method == 'POST': 20 | author = request.user 21 | title = request.POST.get('title', '') 22 | content = request.POST.get('content', '') 23 | if title and content: 24 | post = Post.objects.create(author=author, title=title, content=content) 25 | messages.success(request, 'Post created successfully!') 26 | return redirect('post_detail', pk=post.pk) 27 | else: 28 | messages.error(request, 'Please provide both title and content.') 29 | return render(request, 'post_form.html') 30 | 31 | @login_required(login_url='signin') 32 | def post_update(request, pk): 33 | post = get_object_or_404(Post, pk=pk) 34 | if request.user != post.author: 35 | messages.error(request, 'You are not authorized to edit this post.') 36 | return redirect('post_detail', pk=pk) 37 | 38 | if request.method == 'POST': 39 | title = request.POST.get('title', '') 40 | content = request.POST.get('content', '') 41 | if title and content: 42 | post.title = title 43 | post.content = content 44 | post.save() 45 | messages.success(request, 'Post updated successfully!') 46 | return redirect('post_detail', pk=post.pk) 47 | else: 48 | messages.error(request, 'Please provide both title and content.') 49 | 50 | return render(request, 'post_form.html', {'post': post}) 51 | 52 | @login_required(login_url='signin') 53 | def post_delete(request, pk): 54 | post = get_object_or_404(Post, pk=pk) 55 | if request.user != post.author: 56 | messages.error(request, 'You are not authorized to delete this post.') 57 | return redirect('post_detail', pk=pk) 58 | 59 | if request.method == 'POST': 60 | post.delete() 61 | messages.success(request, 'Post deleted successfully!') 62 | return redirect('post_list') 63 | 64 | return render(request, 'post_confirm_delete.html', {'post': post}) 65 | -------------------------------------------------------------------------------- /config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevOgabek/DjangoBlogPlatform/8dc40b39a6c0e2b44e29f88dd067a12505ac5bbb/config/__init__.py -------------------------------------------------------------------------------- /config/__pycache__/__init__.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevOgabek/DjangoBlogPlatform/8dc40b39a6c0e2b44e29f88dd067a12505ac5bbb/config/__pycache__/__init__.cpython-312.pyc -------------------------------------------------------------------------------- /config/__pycache__/settings.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevOgabek/DjangoBlogPlatform/8dc40b39a6c0e2b44e29f88dd067a12505ac5bbb/config/__pycache__/settings.cpython-312.pyc -------------------------------------------------------------------------------- /config/__pycache__/urls.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevOgabek/DjangoBlogPlatform/8dc40b39a6c0e2b44e29f88dd067a12505ac5bbb/config/__pycache__/urls.cpython-312.pyc -------------------------------------------------------------------------------- /config/__pycache__/wsgi.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevOgabek/DjangoBlogPlatform/8dc40b39a6c0e2b44e29f88dd067a12505ac5bbb/config/__pycache__/wsgi.cpython-312.pyc -------------------------------------------------------------------------------- /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/5.0/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/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for config project. 3 | 4 | Generated by 'django-admin startproject' using Django 5.0.2. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/5.0/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/5.0/ref/settings/ 11 | """ 12 | 13 | from pathlib import Path 14 | 15 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 16 | BASE_DIR = Path(__file__).resolve().parent.parent 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = 'django-insecure-l1*$7z9+j#w%%w$57rklia4=*!wr7mm1h)2r8!x+sv7ebdj^z7' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | 'django.contrib.admin', 35 | 'django.contrib.auth', 36 | 'django.contrib.contenttypes', 37 | 'django.contrib.sessions', 38 | 'django.contrib.messages', 39 | 'django.contrib.staticfiles', 40 | 'blog', 41 | 'user', 42 | ] 43 | 44 | MIDDLEWARE = [ 45 | 'django.middleware.security.SecurityMiddleware', 46 | 'django.contrib.sessions.middleware.SessionMiddleware', 47 | 'django.middleware.common.CommonMiddleware', 48 | 'django.middleware.csrf.CsrfViewMiddleware', 49 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 50 | 'django.contrib.messages.middleware.MessageMiddleware', 51 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 52 | ] 53 | 54 | ROOT_URLCONF = 'config.urls' 55 | 56 | TEMPLATES = [ 57 | { 58 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 59 | 'DIRS': [BASE_DIR / 'template'], 60 | 'APP_DIRS': True, 61 | 'OPTIONS': { 62 | 'context_processors': [ 63 | 'django.template.context_processors.debug', 64 | 'django.template.context_processors.request', 65 | 'django.contrib.auth.context_processors.auth', 66 | 'django.contrib.messages.context_processors.messages', 67 | ], 68 | }, 69 | }, 70 | ] 71 | 72 | WSGI_APPLICATION = 'config.wsgi.application' 73 | 74 | 75 | # Database 76 | # https://docs.djangoproject.com/en/5.0/ref/settings/#databases 77 | 78 | DATABASES = { 79 | 'default': { 80 | 'ENGINE': 'django.db.backends.sqlite3', 81 | 'NAME': BASE_DIR / 'db.sqlite3', 82 | } 83 | } 84 | 85 | 86 | # Password validation 87 | # https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators 88 | 89 | AUTH_PASSWORD_VALIDATORS = [ 90 | { 91 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 92 | }, 93 | { 94 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 95 | }, 96 | { 97 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 98 | }, 99 | { 100 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 101 | }, 102 | ] 103 | 104 | 105 | # Internationalization 106 | # https://docs.djangoproject.com/en/5.0/topics/i18n/ 107 | 108 | LANGUAGE_CODE = 'en-us' 109 | 110 | TIME_ZONE = 'UTC' 111 | 112 | USE_I18N = True 113 | 114 | USE_TZ = True 115 | 116 | 117 | # Static files (CSS, JavaScript, Images) 118 | # https://docs.djangoproject.com/en/5.0/howto/static-files/ 119 | 120 | STATIC_URL = 'static/' 121 | STATICFILES_DIRS = [BASE_DIR / 'static'] 122 | 123 | MEDIA_URL = 'media/' 124 | MEDIA_ROOT = BASE_DIR / 'media' 125 | 126 | AUTH_USER_MODEL = 'user.CustomUser' 127 | 128 | 129 | # Default primary key field type 130 | # https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field 131 | 132 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' 133 | -------------------------------------------------------------------------------- /config/urls.py: -------------------------------------------------------------------------------- 1 | """ 2 | URL configuration for config project. 3 | 4 | The `urlpatterns` list routes URLs to views. For more information please see: 5 | https://docs.djangoproject.com/en/5.0/topics/http/urls/ 6 | Examples: 7 | Function views 8 | 1. Add an import: from my_app import views 9 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 10 | Class-based views 11 | 1. Add an import: from other_app.views import Home 12 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 13 | Including another URLconf 14 | 1. Import the include() function: from django.urls import include, path 15 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 16 | """ 17 | from django.contrib import admin 18 | from django.urls import path, include 19 | from django.conf import settings 20 | from django.conf.urls.static import static 21 | 22 | urlpatterns = [ 23 | path('admin/', admin.site.urls), 24 | path('', include('blog.urls')), 25 | path('user/', include('user.urls')), 26 | 27 | ] 28 | 29 | if settings.DEBUG: 30 | urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) -------------------------------------------------------------------------------- /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/5.0/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 | -------------------------------------------------------------------------------- /db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevOgabek/DjangoBlogPlatform/8dc40b39a6c0e2b44e29f88dd067a12505ac5bbb/db.sqlite3 -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /media/default_profile_picture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevOgabek/DjangoBlogPlatform/8dc40b39a6c0e2b44e29f88dd067a12505ac5bbb/media/default_profile_picture.jpg -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | asgiref==3.7.2 2 | Django==5.0.2 3 | pillow==10.2.0 4 | setuptools==69.1.1 5 | sqlparse==0.4.4 6 | -------------------------------------------------------------------------------- /static/css/message.css: -------------------------------------------------------------------------------- 1 | .success, 2 | .error { 3 | font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 4 | width: 320px; 5 | padding: 12px; 6 | display: flex; 7 | flex-direction: row; 8 | align-items: center; 9 | justify-content: start; 10 | border-radius: 8px; 11 | box-shadow: 0px 0px 5px -3px #111; 12 | position: fixed; 13 | z-index: 9999; 14 | opacity: 0; 15 | animation-duration: 0.5s; 16 | } 17 | 18 | .success { 19 | background: #24d600; 20 | top: 5%; 21 | left: 50%; 22 | transform: translateX(-50%) translateY(-20px); 23 | animation-name: fadeIn, slideIn; 24 | animation-fill-mode: forwards; 25 | } 26 | 27 | .error { 28 | background: #EF665B; 29 | top: 5%; 30 | left: 50%; 31 | transform: translateX(-50%) translateY(-50%); 32 | animation-name: fadeIn, slideIn; 33 | animation-fill-mode: forwards; 34 | } 35 | 36 | .success-hide, 37 | .error-hide { 38 | animation-name: fadeOut, slideOut; 39 | animation-fill-mode: forwards; 40 | } 41 | 42 | .success__icon, 43 | .error__icon { 44 | width: 20px; 45 | height: 20px; 46 | transform: translateY(-2px); 47 | margin-right: 8px; 48 | margin-bottom: -8px; 49 | } 50 | 51 | .success__icon path, 52 | .error__icon path { 53 | fill: #fff; 54 | } 55 | 56 | .success__title, 57 | .error__title { 58 | font-weight: 500; 59 | font-size: 14px; 60 | color: #fff; 61 | } 62 | 63 | .success__close { 64 | width: 20px; 65 | height: 20px; 66 | cursor: pointer; 67 | margin-left: auto; 68 | } 69 | 70 | .error__close { 71 | width: 20px; 72 | height: 20px; 73 | cursor: pointer; 74 | margin-bottom: auto; 75 | margin-left: auto; 76 | } 77 | 78 | .success__close path, 79 | .error__close path { 80 | fill: #fff; 81 | } 82 | 83 | @keyframes fadeIn { 84 | from { 85 | opacity: 0; 86 | } 87 | 88 | to { 89 | opacity: 1; 90 | } 91 | } 92 | 93 | @keyframes fadeOut { 94 | from { 95 | opacity: 1; 96 | } 97 | 98 | to { 99 | opacity: 0; 100 | } 101 | } -------------------------------------------------------------------------------- /static/css/navbar.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Arial, sans-serif; 3 | margin: 0; 4 | padding: 0; 5 | background-color: #f4f4f4; 6 | position: relative; 7 | } 8 | 9 | nav { 10 | background: linear-gradient(to right, #007bff, #0056b3); 11 | padding: 15px 0; 12 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); 13 | } 14 | 15 | .navbar { 16 | display: flex; 17 | justify-content: space-between; 18 | align-items: center; 19 | margin: 0 auto; 20 | padding: 0 20px; 21 | } 22 | 23 | .navbar-brand { 24 | color: #fff; 25 | font-size: 24px; 26 | text-decoration: none; 27 | text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5); 28 | } 29 | 30 | .nav-profile-img img { 31 | width: 40px; 32 | height: 40px; 33 | border-radius: 50%; 34 | border: 1px solid #fff; 35 | margin-left: 20px; 36 | } 37 | 38 | .nav-link { 39 | margin-left: 10px; 40 | } 41 | 42 | .nav-link a { 43 | color: #fff; 44 | text-decoration: none; 45 | padding: 8px 12px; 46 | border-radius: 4px; 47 | background-color: rgba(255, 255, 255, 0.2); 48 | transition: background-color 0.3s; 49 | } 50 | 51 | .nav-link a:hover { 52 | background-color: rgba(255, 255, 255, 0.3); 53 | } 54 | 55 | .nav-link { 56 | display: inline-block; 57 | } 58 | 59 | .nav-link+.nav-link { 60 | margin-left: 10px; 61 | } -------------------------------------------------------------------------------- /static/css/post_confirm_delete.css: -------------------------------------------------------------------------------- 1 | .container { 2 | margin: 50px auto; 3 | padding: 20px; 4 | border: 1px solid #ccc; 5 | border-radius: 5px; 6 | background-color: #fff; 7 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); 8 | max-width: 600px; 9 | } 10 | 11 | h1 { 12 | font-size: 24px; 13 | margin-bottom: 20px; 14 | color: #333; 15 | } 16 | 17 | .confirm-message { 18 | font-size: 16px; 19 | margin-bottom: 30px; 20 | color: #666; 21 | } 22 | 23 | .button-group { 24 | display: flex; 25 | justify-content: space-between; 26 | align-items: center; 27 | } 28 | 29 | .confirm-link, 30 | .cancel-link { 31 | padding: 10px 20px; 32 | border-radius: 5px; 33 | text-decoration: none; 34 | cursor: pointer; 35 | transition: background-color 0.3s; 36 | font-size: 16px; 37 | } 38 | 39 | .confirm-link { 40 | background-color: #dc3545; 41 | color: #fff; 42 | border: none; 43 | } 44 | 45 | .confirm-link:hover { 46 | background-color: #c82333; 47 | } 48 | 49 | .cancel-link { 50 | background-color: #007bff; 51 | color: #fff; 52 | border: none; 53 | } 54 | 55 | .cancel-link:hover { 56 | color: #fff; 57 | background-color: #0056b3; 58 | } 59 | 60 | form input[type="text"], 61 | form input[type="password"], 62 | form textarea { 63 | width: 100%; 64 | padding: 10px; 65 | margin-bottom: 15px; 66 | border: 1px solid #ccc; 67 | border-radius: 5px; 68 | font-size: 16px; 69 | } 70 | 71 | form button[type="submit"] { 72 | padding: 10px 20px; 73 | border-radius: 5px; 74 | border: none; 75 | background-color: #007bff; 76 | color: #fff; 77 | font-size: 16px; 78 | cursor: pointer; 79 | transition: background-color 0.3s; 80 | } 81 | 82 | form button[type="submit"]:hover { 83 | background-color: #0056b3; 84 | } 85 | 86 | a { 87 | color: #007bff; 88 | text-decoration: none; 89 | transition: color 0.3s; 90 | } 91 | 92 | a:hover { 93 | color: #0056b3; 94 | } -------------------------------------------------------------------------------- /static/css/post_detail.css: -------------------------------------------------------------------------------- 1 | .container { 2 | max-width: 800px; 3 | margin: 20px auto; 4 | padding: 20px; 5 | background-color: #fff; 6 | border-radius: 10px; 7 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); 8 | } 9 | 10 | h1 { 11 | font-size: 32px; 12 | margin-bottom: 20px; 13 | } 14 | 15 | .post-meta { 16 | font-size: 14px; 17 | color: #888; 18 | margin-bottom: 20px; 19 | } 20 | 21 | .post-author { 22 | font-weight: bold; 23 | color: #007bff; 24 | margin-right: 10px; 25 | } 26 | 27 | .post-date { 28 | color: #666; 29 | } 30 | 31 | .author-img { 32 | width: 30px; 33 | height: 30px; 34 | border-radius: 50%; 35 | margin-bottom: -8px; 36 | 37 | } 38 | 39 | .post-content { 40 | font-size: 18px; 41 | line-height: 1.6; 42 | margin-bottom: 20px; 43 | } 44 | 45 | .back-link { 46 | display: inline-block; 47 | margin-top: 20px; 48 | font-size: 16px; 49 | color: #007bff; 50 | text-decoration: none; 51 | transition: color 0.3s; 52 | } 53 | 54 | .back-link:hover { 55 | color: #0056b3; 56 | } 57 | 58 | .post-actions { 59 | margin-top: 30px; 60 | } 61 | 62 | .edit-link, 63 | .delete-link { 64 | margin-right: 10px; 65 | font-size: 16px; 66 | color: #007bff; 67 | text-decoration: none; 68 | transition: color 0.3s; 69 | } 70 | 71 | .edit-link:hover, 72 | .delete-link:hover { 73 | color: #0056b3; 74 | } 75 | 76 | a { 77 | text-decoration: none; 78 | } -------------------------------------------------------------------------------- /static/css/post_form.css: -------------------------------------------------------------------------------- 1 | .container { 2 | max-width: 600px; 3 | margin: 20px auto; 4 | padding: 20px; 5 | background-color: #fff; 6 | border-radius: 10px; 7 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); 8 | } 9 | 10 | h1 { 11 | font-size: 28px; 12 | margin-bottom: 20px; 13 | color: #333; 14 | text-align: center; 15 | } 16 | 17 | form { 18 | margin-bottom: 20px; 19 | } 20 | 21 | label { 22 | font-weight: bold; 23 | color: #333; 24 | } 25 | 26 | input[type="text"], 27 | textarea { 28 | width: 100%; 29 | padding: 10px; 30 | margin-bottom: 15px; 31 | border: 1px solid #ccc; 32 | border-radius: 5px; 33 | box-sizing: border-box; 34 | font-size: 16px; 35 | resize: none; 36 | } 37 | 38 | textarea { 39 | height: 200px; 40 | } 41 | 42 | button[type="submit"] { 43 | padding: 10px 20px; 44 | background-color: #007bff; 45 | color: #fff; 46 | border: none; 47 | border-radius: 5px; 48 | cursor: pointer; 49 | font-size: 18px; 50 | transition: background-color 0.3s; 51 | margin-right: 10px; 52 | } 53 | 54 | button[type="submit"]:hover { 55 | background-color: #0056b3; 56 | } 57 | 58 | .cancel-link { 59 | display: inline-block; 60 | padding: 10px 20px; 61 | color: #007bff; 62 | text-decoration: none; 63 | font-size: 18px; 64 | border: 1px solid #007bff; 65 | border-radius: 5px; 66 | transition: color 0.3s, background-color 0.3s; 67 | } 68 | 69 | .cancel-link:hover { 70 | background-color: #007bff; 71 | color: #fff; 72 | } 73 | 74 | .button-group { 75 | text-align: center; 76 | } 77 | 78 | .error-message { 79 | color: red; 80 | font-size: 14px; 81 | } -------------------------------------------------------------------------------- /static/css/post_list.css: -------------------------------------------------------------------------------- 1 | .profile-picture { 2 | position: absolute; 3 | top: 20px; 4 | right: 20px; 5 | width: 50px; 6 | height: 50px; 7 | border-radius: 50%; 8 | } 9 | 10 | .container { 11 | max-width: 800px; 12 | margin: 0 auto; 13 | padding: 20px; 14 | } 15 | 16 | .post { 17 | background-color: #fff; 18 | border-radius: 5px; 19 | margin-bottom: 20px; 20 | padding: 20px; 21 | box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); 22 | } 23 | 24 | .post-title { 25 | font-size: 24px; 26 | margin-bottom: 10px; 27 | } 28 | 29 | .post-content { 30 | font-size: 16px; 31 | color: #555; 32 | } 33 | 34 | .post-author { 35 | display: flex; 36 | align-items: center; 37 | font-size: 14px; 38 | color: #777; 39 | margin-top: 10px; 40 | } 41 | 42 | .author-img { 43 | width: 30px; 44 | height: 30px; 45 | border-radius: 50%; 46 | margin-right: 10px; 47 | } 48 | 49 | .post-date { 50 | font-size: 12px; 51 | color: #999; 52 | } 53 | 54 | .create-post-link { 55 | display: block; 56 | margin-top: 20px; 57 | text-align: center; 58 | font-size: 16px; 59 | color: #007bff; 60 | text-decoration: none; 61 | } 62 | 63 | a { 64 | text-decoration: none; 65 | color: #0056b3; 66 | } 67 | 68 | .post-views { 69 | font-size: 12px; 70 | color: #999; 71 | } -------------------------------------------------------------------------------- /static/css/profile.css: -------------------------------------------------------------------------------- 1 | .container { 2 | margin: 50px auto; 3 | padding: 20px; 4 | border-radius: 10px; 5 | background-color: #f9f9f9; 6 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); 7 | max-width: 600px; 8 | text-align: center; 9 | } 10 | 11 | h1 { 12 | font-size: 28px; 13 | margin-bottom: 20px; 14 | color: #333; 15 | } 16 | 17 | h2 { 18 | font-size: 24px; 19 | margin-bottom: 10px; 20 | color: #333; 21 | } 22 | 23 | p { 24 | font-size: 18px; 25 | margin-bottom: 10px; 26 | color: #666; 27 | text-align: center; 28 | } 29 | 30 | img { 31 | display: block; 32 | margin: 0 auto 20px; 33 | width: 200px; 34 | height: 200px; 35 | border-radius: 50%; 36 | object-fit: cover; 37 | 38 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); 39 | } 40 | 41 | .settings-button, 42 | .logout-button { 43 | display: inline-block; 44 | padding: 12px 24px; 45 | border-radius: 8px; 46 | text-decoration: none; 47 | font-size: 18px; 48 | font-weight: bold; 49 | transition: background-color 0.3s, color 0.3s, box-shadow 0.3s; 50 | border: none; 51 | outline: none; 52 | cursor: pointer; 53 | } 54 | 55 | .settings-button { 56 | background-color: #007bff; 57 | color: #fff; 58 | margin-right: 10px; 59 | } 60 | 61 | .settings-button:hover { 62 | background-color: #0056b3; 63 | } 64 | 65 | .logout-button { 66 | background-color: #dc3545; 67 | color: #fff; 68 | } 69 | 70 | .logout-button:hover { 71 | background-color: #c82333; 72 | } -------------------------------------------------------------------------------- /static/css/registrations/style.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | justify-content: center; 4 | align-items: center; 5 | height: 100vh; 6 | } 7 | 8 | .form-box { 9 | max-width: 350px; 10 | background: #f2f2f2; 11 | overflow: hidden; 12 | border-radius: 16px; 13 | color: #333; 14 | margin-top: 10%; 15 | margin-left: auto; 16 | margin-right: auto; 17 | text-align: center; 18 | } 19 | 20 | .form { 21 | position: relative; 22 | display: flex; 23 | flex-direction: column; 24 | padding: 32px 24px 24px; 25 | gap: 16px; 26 | text-align: center; 27 | } 28 | 29 | .title { 30 | font-weight: bold; 31 | font-size: 1.6rem; 32 | color: #000; 33 | } 34 | 35 | .subtitle { 36 | font-size: 1rem; 37 | color: #666; 38 | } 39 | 40 | .form-container { 41 | overflow: hidden; 42 | border-radius: 8px; 43 | margin: 1rem 0 .5rem; 44 | width: 100%; 45 | } 46 | 47 | .input { 48 | border: 0; 49 | outline: 0; 50 | height: 40px; 51 | width: 100%; 52 | border-bottom: 1px solid #e0e0e0; 53 | font-size: .9rem; 54 | padding: 8px 15px; 55 | color: #333; 56 | } 57 | 58 | .form-section { 59 | padding: 16px; 60 | font-size: .85rem; 61 | background-color: #f5f9ff; 62 | color: #333; 63 | } 64 | 65 | .form-section a { 66 | font-weight: bold; 67 | color: #007bff; 68 | transition: color .3s ease; 69 | } 70 | 71 | .form-section a:hover { 72 | color: #0056b3; 73 | text-decoration: underline; 74 | } 75 | 76 | .form button { 77 | background-color: #007bff; 78 | color: #fff; 79 | border: 0; 80 | border-radius: 24px; 81 | padding: 10px 16px; 82 | font-size: 1rem; 83 | font-weight: 600; 84 | } 85 | 86 | .input::placeholder { 87 | color: #999; 88 | } 89 | 90 | a { 91 | text-decoration: none; 92 | color: #333; 93 | } -------------------------------------------------------------------------------- /static/css/settings.css: -------------------------------------------------------------------------------- 1 | .container { 2 | margin: 50px auto; 3 | padding: 20px; 4 | border-radius: 10px; 5 | background-color: #f9f9f9; 6 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); 7 | max-width: 600px; 8 | } 9 | 10 | h1 { 11 | font-size: 28px; 12 | margin-bottom: 20px; 13 | color: #333; 14 | } 15 | 16 | form { 17 | text-align: left; 18 | } 19 | 20 | label { 21 | font-size: 18px; 22 | color: #333; 23 | margin-bottom: 10px; 24 | display: block; 25 | } 26 | 27 | input[type="text"], 28 | textarea { 29 | width: 100%; 30 | padding: 12px; 31 | margin-bottom: 20px; 32 | border-radius: 8px; 33 | border: 1px solid #ccc; 34 | font-size: 16px; 35 | box-sizing: border-box; 36 | } 37 | 38 | textarea { 39 | max-width: 600px; 40 | max-height: 200px; 41 | } 42 | 43 | input[type="file"] { 44 | margin-top: 10px; 45 | } 46 | 47 | button[type="submit"] { 48 | padding: 14px 24px; 49 | border-radius: 8px; 50 | border: none; 51 | background-color: #007bff; 52 | color: #fff; 53 | font-size: 18px; 54 | cursor: pointer; 55 | transition: background-color 0.3s; 56 | } 57 | 58 | button[type="submit"]:hover { 59 | background-color: #0056b3; 60 | } -------------------------------------------------------------------------------- /static/js/message.js: -------------------------------------------------------------------------------- 1 | document.addEventListener('DOMContentLoaded', function () { 2 | var closeButton = document.querySelector('.success__close'); 3 | var successMessage = document.querySelector('.success'); 4 | closeButton.addEventListener('click', function () { 5 | successMessage.classList.add('success-hide'); 6 | setTimeout(function () { 7 | successMessage.style.display = 'none'; 8 | }, 500); 9 | }); 10 | }); 11 | 12 | document.addEventListener('DOMContentLoaded', function () { 13 | var closeButton = document.querySelector('.error__close'); 14 | var errorMessage = document.querySelector('.error'); 15 | 16 | closeButton.addEventListener('click', function () { 17 | errorMessage.classList.add('error-hide'); 18 | setTimeout(function () { 19 | errorMessage.style.display = 'none'; 20 | }, 500); 21 | }); 22 | }); -------------------------------------------------------------------------------- /template/base.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | {% block title %} 10 | 11 | {% endblock title %} 12 | 13 | 14 | 15 | 16 | {% block link %} 17 | 18 | {% endblock link %} 19 | 20 | 21 | 22 | 39 | {% if messages %} 40 | {% for message in messages %} 41 | {% if message.tags == 'error' %} 42 | 43 |
44 |
45 | 47 | 49 | 50 |
51 |
{{ message }}
52 |
53 | 54 | 56 | 57 | 58 |
59 |
60 | {% elif message.tags == 'success' %} 61 |
62 |
63 | 65 | 67 | 68 | 69 |
70 |
{{ message }}
71 |
72 | 73 | 75 | 76 | 77 |
78 |
79 | {% endif %} 80 | {% endfor %} 81 | {% endif %} 82 |
83 | {% block content %} 84 | 85 | {% endblock content %} 86 |
87 | 88 | 89 | -------------------------------------------------------------------------------- /template/post_confirm_delete.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load static %} 3 | {% block title %} 4 | Delete Post 5 | {% endblock title %} 6 | {% block link %} 7 | 8 | {% endblock link %} 9 | 10 | {% block content %} 11 |
12 |

Delete Post

13 |

Are you sure you want to delete this post?

14 |
15 | {% csrf_token %} 16 | 17 | Cancel 18 |
19 |
20 | {% endblock content %} -------------------------------------------------------------------------------- /template/post_detail.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load static %} 3 | {% block title %} 4 | {{ post.title }} 5 | {% endblock title %} 6 | {% block link %} 7 | 8 | {% endblock link %} 9 | 10 | {% block content %} 11 |
12 |

{{ post.title }}

13 | 26 |
{{ post.content }}
27 | {% if user == post.author %} 28 |
29 | Edit 30 | Delete 31 |
32 | {% endif %} 33 | 34 | Back to Post List 35 |
36 | {% endblock content %} -------------------------------------------------------------------------------- /template/post_form.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load static %} 3 | {% block title %} 4 | {% if post.pk %}Update{% else %}Create{% endif %} Post 5 | {% endblock title %} 6 | {% block link %} 7 | 8 | {% endblock link %} 9 | {% block content %} 10 |
11 |

{% if post.pk %}Update{% else %}Create{% endif %} Post

12 |
14 | {% csrf_token %} 15 | 16 | 17 |
{{ post.title.errors }}
18 | 19 | 20 | 21 |
{{ post.content.errors }}
22 | 23 |
24 | 25 | Cancel 26 |
27 |
28 |
29 | {% endblock content %} -------------------------------------------------------------------------------- /template/post_list.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load static %} 3 | {% block title %} 4 | Blog - Post List 5 | {% endblock title %} 6 | {% block link %} 7 | 8 | 9 | {% endblock link %} 10 | 11 | {% block content %} 12 |
13 |

Blog - Post List

14 | {% for post in posts %} 15 |
16 | 17 |

{{ post.title }}

18 |
19 |

{{ post.content }}

20 | 21 | 25 | 26 | 27 | Views: {{ post.views }} 28 |
29 | {% endfor %} 30 | {% if user.is_authenticated %} 31 | Create New Post 32 | {% endif %} 33 |
34 | {% endblock content %} -------------------------------------------------------------------------------- /template/profile.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load static %} 3 | {% block title %} 4 | User Profile 5 | {% endblock title %} 6 | {% block link %} 7 | 8 | {% endblock link %} 9 | 10 | {% block content %} 11 |
12 |

User Profile

13 |
14 |

{{ user_profile.username }}

15 |

Bio: {{ user_profile.bio }}

16 | {% if user_profile.profile_picture %} 17 | Profile Picture 18 | {% else %} 19 |

No profile picture available

20 | {% endif %} 21 | {% if user.is_authenticated %} 22 | {% if user == user_profile %} 23 | Update Profile 24 | Log out 25 | {% endif %} 26 | {% endif %} 27 |
28 |
29 | {% endblock content %} -------------------------------------------------------------------------------- /template/registrations/signin.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load static %} 3 | {% block title %} 4 | Sign In 5 | {% endblock title %} 6 | {% block link %} 7 | 8 | 9 | {% endblock link %} 10 | 11 | {% block content %} 12 |
13 |
14 | Sign In 15 | Log in to your account 16 | {% csrf_token %} 17 |
18 | 19 | 20 |
21 | 22 |
23 |
24 | Don't have an account? Sign 25 | Up
26 |
27 |
28 | {% endblock content %} -------------------------------------------------------------------------------- /template/registrations/signup.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load static %} 3 | {% block title %} 4 | Sign Up 5 | {% endblock title %} 6 | {% block link %} 7 | 8 | 9 | {% endblock link %} 10 | 11 | {% block content %} 12 | 13 |
14 |
15 | Sign Up 16 | Create a new account 17 | {% csrf_token %} 18 |
19 | 20 | 22 | 24 | 26 | 27 | 28 |
29 | 30 |
31 |
32 | Have an account? Sign In 33 |
34 |
35 | {% endblock content %} -------------------------------------------------------------------------------- /template/settings.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load static %} 3 | {% block title %} 4 | Settings 5 | {% endblock title %} 6 | {% block link %} 7 | 8 | {% endblock link %} 9 | 10 | {% block content %} 11 |
12 |

Settings

13 |
14 | {% csrf_token %} 15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |

23 | 24 |
25 |
26 | {% endblock content %} -------------------------------------------------------------------------------- /user/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevOgabek/DjangoBlogPlatform/8dc40b39a6c0e2b44e29f88dd067a12505ac5bbb/user/__init__.py -------------------------------------------------------------------------------- /user/__pycache__/__init__.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevOgabek/DjangoBlogPlatform/8dc40b39a6c0e2b44e29f88dd067a12505ac5bbb/user/__pycache__/__init__.cpython-312.pyc -------------------------------------------------------------------------------- /user/__pycache__/admin.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevOgabek/DjangoBlogPlatform/8dc40b39a6c0e2b44e29f88dd067a12505ac5bbb/user/__pycache__/admin.cpython-312.pyc -------------------------------------------------------------------------------- /user/__pycache__/apps.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevOgabek/DjangoBlogPlatform/8dc40b39a6c0e2b44e29f88dd067a12505ac5bbb/user/__pycache__/apps.cpython-312.pyc -------------------------------------------------------------------------------- /user/__pycache__/models.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevOgabek/DjangoBlogPlatform/8dc40b39a6c0e2b44e29f88dd067a12505ac5bbb/user/__pycache__/models.cpython-312.pyc -------------------------------------------------------------------------------- /user/__pycache__/urls.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevOgabek/DjangoBlogPlatform/8dc40b39a6c0e2b44e29f88dd067a12505ac5bbb/user/__pycache__/urls.cpython-312.pyc -------------------------------------------------------------------------------- /user/__pycache__/views.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevOgabek/DjangoBlogPlatform/8dc40b39a6c0e2b44e29f88dd067a12505ac5bbb/user/__pycache__/views.cpython-312.pyc -------------------------------------------------------------------------------- /user/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import CustomUser 3 | 4 | admin.site.register(CustomUser) -------------------------------------------------------------------------------- /user/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class UserConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'user' 7 | -------------------------------------------------------------------------------- /user/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0.2 on 2024-02-23 06:04 2 | 3 | import django.contrib.auth.models 4 | import django.contrib.auth.validators 5 | import django.utils.timezone 6 | from django.db import migrations, models 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | initial = True 12 | 13 | dependencies = [ 14 | ('auth', '0012_alter_user_first_name_max_length'), 15 | ] 16 | 17 | operations = [ 18 | migrations.CreateModel( 19 | name='CustomUser', 20 | fields=[ 21 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 22 | ('password', models.CharField(max_length=128, verbose_name='password')), 23 | ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), 24 | ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), 25 | ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), 26 | ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), 27 | ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), 28 | ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), 29 | ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), 30 | ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), 31 | ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), 32 | ('bio', models.TextField(blank=True)), 33 | ('profile_picture', models.ImageField(blank=True, default='default_profile_picture.jpg', null=True, upload_to='profile_pictures/')), 34 | ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), 35 | ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), 36 | ], 37 | options={ 38 | 'verbose_name': 'user', 39 | 'verbose_name_plural': 'users', 40 | 'abstract': False, 41 | }, 42 | managers=[ 43 | ('objects', django.contrib.auth.models.UserManager()), 44 | ], 45 | ), 46 | ] 47 | -------------------------------------------------------------------------------- /user/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevOgabek/DjangoBlogPlatform/8dc40b39a6c0e2b44e29f88dd067a12505ac5bbb/user/migrations/__init__.py -------------------------------------------------------------------------------- /user/migrations/__pycache__/0001_initial.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevOgabek/DjangoBlogPlatform/8dc40b39a6c0e2b44e29f88dd067a12505ac5bbb/user/migrations/__pycache__/0001_initial.cpython-312.pyc -------------------------------------------------------------------------------- /user/migrations/__pycache__/__init__.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevOgabek/DjangoBlogPlatform/8dc40b39a6c0e2b44e29f88dd067a12505ac5bbb/user/migrations/__pycache__/__init__.cpython-312.pyc -------------------------------------------------------------------------------- /user/models.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import AbstractUser 2 | from django.db import models 3 | 4 | class CustomUser(AbstractUser): 5 | bio = models.TextField(blank=True) 6 | profile_picture = models.ImageField(upload_to='profile_pictures/', blank=True, null=True, default='default_profile_picture.jpg') 7 | 8 | def __str__(self): 9 | return self.username 10 | -------------------------------------------------------------------------------- /user/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /user/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from . import views 3 | 4 | urlpatterns = [ 5 | path('profile//', views.profile, name='profile'), 6 | path('settings/', views.settings, name='settings'), 7 | path('signin/', views.signin, name='signin'), 8 | path('signup/', views.signup, name='signup'), 9 | path('logout/', views.logout, name='logout'), 10 | ] -------------------------------------------------------------------------------- /user/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render, redirect, get_object_or_404 2 | from django.contrib.auth.decorators import login_required 3 | from django.contrib.auth import get_user_model 4 | from django.contrib.auth.models import auth 5 | from django.contrib import messages 6 | from .models import CustomUser 7 | import re 8 | 9 | CustomUser = get_user_model() 10 | 11 | 12 | def check_password_strength(password): 13 | if len(password) < 8: 14 | return False 15 | 16 | if not re.search(r"[A-Z]", password): 17 | return False 18 | 19 | if not re.search(r"[a-z]", password): 20 | return False 21 | 22 | if not re.search(r"[0-9]", password): 23 | return False 24 | 25 | if not re.search(r'[!@#$%^&*(),.?":{}|<>]', password): 26 | return False 27 | 28 | return True 29 | 30 | 31 | def signup(request): 32 | if request.method == "POST": 33 | username = request.POST.get("username") 34 | email_input = request.POST.get("email_input") 35 | first_name = request.POST.get("first_name") 36 | last_name = request.POST.get("last_name") 37 | password = request.POST.get("password") 38 | confirm_password = request.POST.get("confirm_password") 39 | 40 | if password != confirm_password: 41 | messages.error(request, "Passwords do not match") 42 | elif not check_password_strength(password): 43 | messages.error(request, "Password is not strong enough") 44 | elif CustomUser.objects.filter(email=email_input).exists(): 45 | messages.error(request, "Email Taken") 46 | elif CustomUser.objects.filter(username=username).exists(): 47 | messages.error(request, "Username Taken") 48 | else: 49 | user = CustomUser.objects.create_user( 50 | username=username, 51 | email=email_input, 52 | password=password, 53 | first_name=first_name, 54 | last_name=last_name, 55 | ) 56 | if user is not None: 57 | messages.success(request, "Account created successfully!") 58 | return redirect("signin") 59 | else: 60 | messages.error( 61 | request, "Failed to create account. Please try again later." 62 | ) 63 | 64 | return render( 65 | request, 66 | "registrations/signup.html", 67 | { 68 | "username": username, 69 | "email_input": email_input, 70 | "first_name": first_name, 71 | "last_name": last_name, 72 | }, 73 | ) 74 | else: 75 | return render(request, "registrations/signup.html") 76 | 77 | 78 | @login_required(login_url="signin") 79 | def logout(request): 80 | auth.logout(request) 81 | return redirect("signin") 82 | 83 | 84 | def signin(request): 85 | if request.method == "POST": 86 | username = request.POST.get("username") 87 | password = request.POST.get("password") 88 | 89 | user = auth.authenticate(username=username, password=password) 90 | 91 | if user is not None: 92 | auth.login(request, user) 93 | return redirect("post_list") 94 | else: 95 | messages.error(request, "Invalid Credentials") 96 | return render(request, "registrations/signin.html", {"username": username}) 97 | else: 98 | return render(request, "registrations/signin.html") 99 | 100 | 101 | def profile(request, pk): 102 | user_profile = get_object_or_404(CustomUser, pk=pk) 103 | return render(request, "profile.html", {"user_profile": user_profile}) 104 | 105 | 106 | @login_required(login_url="signin") 107 | def settings(request): 108 | user_profile = request.user 109 | 110 | if request.method == "POST": 111 | first_name = request.POST.get("first_name", "") 112 | last_name = request.POST.get("last_name", "") 113 | bio = request.POST.get("bio", "") 114 | 115 | user_profile.first_name = first_name 116 | user_profile.last_name = last_name 117 | user_profile.bio = bio 118 | 119 | if "profile_picture" in request.FILES: 120 | profile_picture = request.FILES["profile_picture"] 121 | user_profile.profile_picture = profile_picture 122 | 123 | user_profile.save() 124 | 125 | return redirect("profile", user_profile.pk) 126 | 127 | return render(request, "settings.html", {"user_profile": user_profile}) --------------------------------------------------------------------------------