├── src ├── static │ └── new ├── blog │ ├── __init__.py │ ├── migrations │ │ ├── __init__.py │ │ └── 0001_initial.py │ ├── tests.py │ ├── utils.py │ ├── apps.py │ ├── admin.py │ ├── urls.py │ ├── signals.py │ ├── templates │ │ └── blog │ │ │ ├── post_create.html │ │ │ ├── post_update.html │ │ │ ├── post_delete.html │ │ │ ├── post_list.html │ │ │ └── post_detail.html │ ├── forms.py │ ├── static │ │ └── blog │ │ │ └── main.css │ ├── models.py │ └── views.py ├── cblog │ ├── __init__.py │ ├── storages.py │ ├── asgi.py │ ├── wsgi.py │ ├── urls.py │ └── settings.py ├── users │ ├── __init__.py │ ├── migrations │ │ ├── __init__.py │ │ └── 0001_initial.py │ ├── tests.py │ ├── admin.py │ ├── apps.py │ ├── templates │ │ └── users │ │ │ ├── password_reset_complete.html │ │ │ ├── logout.html │ │ │ ├── password_reset_done.html │ │ │ ├── password_reset_email.html │ │ │ ├── password_reset_confirm.html │ │ │ ├── register.html │ │ │ ├── login.html │ │ │ └── profile.html │ ├── signals.py │ ├── models.py │ ├── urls.py │ ├── views.py │ └── forms.py ├── .env ├── media_root │ ├── avatar.png │ ├── django.jpg │ ├── blog │ │ └── 1 │ │ │ ├── profile.jpg │ │ │ └── crud_resize.png │ └── user │ │ ├── 1 │ │ └── profile_2.png │ │ └── 10 │ │ └── profile.jpg ├── manage.py └── templates │ ├── navbar.html │ └── base.html ├── .gitignore ├── 1.png ├── outcome.png ├── project.jpg ├── requirements.txt ├── S3_Static_Website ├── sorry.jpg └── index.html ├── s3policy.json ├── userdata.sh ├── lambda_function.py ├── LICENSE ├── developer_notes.md └── README.md /src/static/new: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/blog/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/cblog/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/users/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | src/.env 3 | -------------------------------------------------------------------------------- /src/blog/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/users/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devenes/my-aws-capstone-project/HEAD/1.png -------------------------------------------------------------------------------- /outcome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devenes/my-aws-capstone-project/HEAD/outcome.png -------------------------------------------------------------------------------- /project.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devenes/my-aws-capstone-project/HEAD/project.jpg -------------------------------------------------------------------------------- /src/blog/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /src/users/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devenes/my-aws-capstone-project/HEAD/requirements.txt -------------------------------------------------------------------------------- /src/.env: -------------------------------------------------------------------------------- 1 | SECRET_KEY=i2^$(%im!!)@lwenn9nk40%yo#ay-lqs_#3p=v(^7-1-%ck$y@ 2 | PASSWORD=Devenes123456 3 | -------------------------------------------------------------------------------- /src/media_root/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devenes/my-aws-capstone-project/HEAD/src/media_root/avatar.png -------------------------------------------------------------------------------- /src/media_root/django.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devenes/my-aws-capstone-project/HEAD/src/media_root/django.jpg -------------------------------------------------------------------------------- /S3_Static_Website/sorry.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devenes/my-aws-capstone-project/HEAD/S3_Static_Website/sorry.jpg -------------------------------------------------------------------------------- /src/media_root/blog/1/profile.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devenes/my-aws-capstone-project/HEAD/src/media_root/blog/1/profile.jpg -------------------------------------------------------------------------------- /src/media_root/user/1/profile_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devenes/my-aws-capstone-project/HEAD/src/media_root/user/1/profile_2.png -------------------------------------------------------------------------------- /src/media_root/user/10/profile.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devenes/my-aws-capstone-project/HEAD/src/media_root/user/10/profile.jpg -------------------------------------------------------------------------------- /src/media_root/blog/1/crud_resize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devenes/my-aws-capstone-project/HEAD/src/media_root/blog/1/crud_resize.png -------------------------------------------------------------------------------- /src/blog/utils.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | 4 | def get_random_code(): 5 | code = str(uuid.uuid4())[:11].replace("-", "") 6 | return code 7 | -------------------------------------------------------------------------------- /src/users/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Profile 3 | 4 | admin.site.register(Profile) 5 | 6 | # Register your models here. 7 | -------------------------------------------------------------------------------- /src/blog/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class BlogConfig(AppConfig): 5 | name = 'blog' 6 | 7 | def ready(self): 8 | import blog.signals 9 | -------------------------------------------------------------------------------- /src/cblog/storages.py: -------------------------------------------------------------------------------- 1 | from storages.backends.s3boto3 import S3Boto3Storage 2 | 3 | 4 | class MediaStore(S3Boto3Storage): 5 | location = 'media' 6 | file_overwrite = False 7 | -------------------------------------------------------------------------------- /src/users/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class UsersConfig(AppConfig): 5 | name = 'users' 6 | 7 | def ready(self): 8 | import users.signals 9 | -------------------------------------------------------------------------------- /src/blog/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Post, Like, PostView, Comment 3 | 4 | admin.site.register(Post) 5 | admin.site.register(Like) 6 | admin.site.register(PostView) 7 | admin.site.register(Comment) 8 | 9 | # Register your models here. 10 | -------------------------------------------------------------------------------- /S3_Static_Website/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | FAILOVER SCENARIO 5 | 6 | 7 | 8 |
9 |

FAILOVER SCENARIO

10 |
11 | 12 |
13 | VW is the best 14 | 15 | 16 | 4 | Your password has been set successfully! 5 | 6 | Sign In Here 7 | 8 | {% endblock %} -------------------------------------------------------------------------------- /s3policy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Id": "Policy1649009833841", 4 | "Statement": [ 5 | { 6 | "Sid": "Stmt1649009825154", 7 | "Effect": "Allow", 8 | "Principal": "*", 9 | "Action": "s3:GetObject", 10 | "Resource": "arn:aws:s3:::www.caucasusllc.com/*" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /src/users/templates/users/logout.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} {% block title %}login{% endblock %} 2 | {% block content %} 3 |

You have been logged out

4 |
5 | 6 | Log in Again 7 | 8 |
9 | 10 | {% endblock %} -------------------------------------------------------------------------------- /src/users/signals.py: -------------------------------------------------------------------------------- 1 | from django.db.models.signals import post_save 2 | from django.contrib.auth.models import User 3 | from django.dispatch import receiver 4 | from .models import Profile 5 | 6 | 7 | @receiver(post_save, sender=User) 8 | def create_profile(sender, instance, created, **kwargs): 9 | if created: 10 | Profile.objects.create(user=instance) 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/cblog/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for cblog 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/3.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', 'cblog.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /src/cblog/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for cblog 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/3.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', 'cblog.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /src/users/templates/users/password_reset_done.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} {% block title %}login{% endblock %} 2 | {% block content %} 3 |
4 |

We’ve emailed you instructions for setting your password, if an account exists with the email you entered. You 5 | should receive them shortly.

6 | 7 |

If you don’t receive an email, please make sure you’ve entered the address you registered with, and check your 8 | spam folder.

9 |
10 | 11 | {% endblock %} -------------------------------------------------------------------------------- /src/blog/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from .views import post_list, post_create, post_detail, post_update, post_delete, like 3 | 4 | 5 | app_name = "blog" 6 | urlpatterns = [ 7 | path("", post_list, name="list"), 8 | path("create/", post_create, name="create"), 9 | path("/", post_detail, name="detail"), 10 | path("/update/", post_update, name="update"), 11 | path("/delete/", post_delete, name="delete"), 12 | path("/like/", like, name="like"), 13 | ] 14 | -------------------------------------------------------------------------------- /src/blog/signals.py: -------------------------------------------------------------------------------- 1 | from django.db.models.signals import pre_save 2 | from django.dispatch import receiver 3 | from django.template.defaultfilters import slugify 4 | from .models import Post 5 | from .utils import get_random_code 6 | 7 | 8 | @receiver(pre_save, sender=Post) 9 | def pre_save_create_slug(sender, instance, **kwargs): 10 | if not instance.slug: 11 | instance.slug = slugify( 12 | instance.title + " " + get_random_code()) 13 | 14 | 15 | # a = "henry forecter" 16 | 17 | # print(slugify(a)) == > henry-forester 18 | -------------------------------------------------------------------------------- /src/blog/templates/blog/post_create.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load crispy_forms_tags %} 3 | 4 | {% block content %} 5 |
6 |
7 |

Blog Post

8 |
9 |
10 | {% csrf_token %} 11 | {{form|crispy}} 12 |
13 | 14 |
15 |
16 |
17 | 18 | {% endblock content %} -------------------------------------------------------------------------------- /src/blog/templates/blog/post_update.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load crispy_forms_tags %} 3 | 4 | {% block content %} 5 |
6 |
7 |

Update Post

8 |
9 |
10 | {% csrf_token %} 11 | {{form|crispy}} 12 |
13 | 14 |
15 |
16 |
17 | 18 | {% endblock content %} -------------------------------------------------------------------------------- /src/users/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.auth.models import User 3 | 4 | 5 | def user_profile_path(instance, filename): 6 | return 'user/{0}/{1}'.format(instance.user.id, filename) 7 | 8 | 9 | class Profile(models.Model): 10 | user = models.OneToOneField(User, on_delete=models.CASCADE) 11 | image = models.ImageField( 12 | upload_to=user_profile_path, default="avatar.png") 13 | bio = models.TextField(blank=True) 14 | 15 | def __str__(self): 16 | return "{} {}".format(self.user, 'Profile') 17 | -------------------------------------------------------------------------------- /src/blog/templates/blog/post_delete.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} {% load static %} {% block content %} 2 | 3 |
4 |
5 |
6 |

Are you sure you want to delete "{{object}}"?

7 | 8 |
9 | {% csrf_token %} 10 | Cancel 11 | 12 |
13 | 14 |
15 |
16 |
17 | {% endblock %} -------------------------------------------------------------------------------- /userdata.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | apt-get update -y 3 | apt-get install git -y 4 | apt-get install python3 -y 5 | cd /home/ubuntu/ 6 | TOKEN="*****************************************" 7 | git clone https://$TOKEN@github.com/devenes/my-aws-capstone-project.git 8 | cd /home/ubuntu/my-aws-capstone-project 9 | apt install python3-pip -y 10 | apt-get install python3.7-dev default-libmysqlclient-dev -y 11 | pip3 install -r requirements.txt 12 | cd /home/ubuntu/my-aws-capstone-project/src 13 | python3 manage.py collectstatic --noinput 14 | python3 manage.py makemigrations 15 | python3 manage.py migrate 16 | python3 manage.py runserver 0.0.0.0:80 -------------------------------------------------------------------------------- /src/users/templates/users/password_reset_email.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} {% block title %}login{% endblock %} 2 | {% load crispy_forms_tags %} 3 | {% block content %} 4 |
5 |
6 | {% csrf_token %} 7 |
8 | Reset Password 9 | {{ form|crispy }} 10 |
11 |
12 | 13 |
14 |
15 | 16 |
17 | {% endblock %} -------------------------------------------------------------------------------- /src/blog/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.db.models import fields 3 | from .models import Post, Comment 4 | 5 | 6 | class PostForm(forms.ModelForm): 7 | status = forms.ChoiceField(choices=Post.OPTIONS) 8 | category = forms.ChoiceField(choices=Post.CATEGORY_OPT) 9 | 10 | class Meta: 11 | model = Post 12 | fields = ( 13 | 'title', 14 | 'content', 15 | 'image', 16 | 'category', 17 | 'status', 18 | ) 19 | 20 | 21 | class CommentForm(forms.ModelForm): 22 | class Meta: 23 | model = Comment 24 | fields = ('content',) 25 | -------------------------------------------------------------------------------- /src/users/templates/users/password_reset_confirm.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} {% block title %}login{% endblock %} 2 | {% load crispy_forms_tags %} 3 | {% block content %} 4 |
5 |
6 | {% csrf_token %} 7 |
8 | Reset Password 9 | {{ form|crispy }} 10 |
11 |
12 | 13 |
14 |
15 | 16 |
17 | {% endblock %} -------------------------------------------------------------------------------- /src/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', 'cblog.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 | -------------------------------------------------------------------------------- /lambda_function.py: -------------------------------------------------------------------------------- 1 | import json 2 | import boto3 3 | 4 | 5 | def lambda_handler(event, context): 6 | s3 = boto3.client("s3") 7 | 8 | if event: 9 | print("Event: ", event) 10 | filename = str(event['Records'][0]['s3']['object']['key']) 11 | timestamp = str(event['Records'][0]['eventTime']) 12 | event_name = str(event['Records'][0]['eventName']).split(':')[0][6:] 13 | 14 | filename1 = filename.split('/') 15 | filename2 = filename1[-1] 16 | 17 | dynamo_db = boto3.resource('dynamodb') 18 | dynamoTable = dynamo_db.Table('awscapstoneDynamo') 19 | 20 | dynamoTable.put_item(Item={ 21 | 'id': filename2, 22 | 'timestamp': timestamp, 23 | 'Event': event_name, 24 | }) 25 | 26 | return "Lambda success" 27 | -------------------------------------------------------------------------------- /src/users/templates/users/register.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} {% block title %}Register{% endblock %} 2 | {% load crispy_forms_tags %} 3 | {% block content %} 4 |
5 |
6 | {% csrf_token %} 7 |
8 | Join Today 9 | {{ form|crispy }} 10 |
11 |
12 | 13 |
14 |
15 |
16 | 17 | Already have an account?Sign In 18 | 19 |
20 | 21 |
22 | {% endblock %} -------------------------------------------------------------------------------- /src/blog/static/blog/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #fafafa; 3 | color: #333333; 4 | margin-top: 5rem; 5 | } 6 | 7 | h1, 8 | h2, 9 | h3, 10 | h4, 11 | h5, 12 | h6 { 13 | color: #444444; 14 | } 15 | 16 | ul { 17 | margin: 0; 18 | } 19 | 20 | .bg-steel { 21 | background-color: #5f788a; 22 | } 23 | 24 | .site-header .navbar-nav .nav-link { 25 | color: #cbd5db; 26 | } 27 | 28 | .site-header .navbar-nav .nav-link:hover { 29 | color: #ffffff; 30 | } 31 | 32 | .site-header .navbar-nav .nav-link.active { 33 | font-weight: 500; 34 | } 35 | 36 | .content-section { 37 | background: #ffffff; 38 | padding: 10px 20px; 39 | border: 1px solid #dddddd; 40 | border-radius: 3px; 41 | margin-bottom: 20px; 42 | } 43 | 44 | .account-img { 45 | height: 110px; 46 | width: 110px; 47 | margin-right: 20px; 48 | margin-bottom: 16px; 49 | } 50 | 51 | .account-heading { 52 | font-size: 2.5rem; 53 | } 54 | -------------------------------------------------------------------------------- /src/users/templates/users/login.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} {% block title %}login{% endblock %} 2 | {% load crispy_forms_tags %} 3 | {% block content %} 4 |
5 |
6 | {% csrf_token %} 7 |
8 | Login 9 | {{ form|crispy }} 10 |
11 |
12 | 13 | 14 | Forgot Password? 15 | 16 |
17 |
18 |
19 | 20 | Need an account?Sign Up Now 21 | 22 |
23 | 24 |
25 | {% endblock %} -------------------------------------------------------------------------------- /src/users/templates/users/profile.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load crispy_forms_tags %} 3 | {% block content %} 4 |
5 |
6 | 7 |
8 | 9 |

{{ user.email }}

10 |
11 |
12 |
13 | {% csrf_token %} 14 |
15 | Profile 16 | {{ u_form|crispy }} 17 | {{ p_form|crispy }} 18 |
19 |
20 | 21 |
22 |
23 |
24 | {% endblock content %} -------------------------------------------------------------------------------- /src/users/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.4 on 2021-02-28 14:30 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | import users.models 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | initial = True 12 | 13 | dependencies = [ 14 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 15 | ] 16 | 17 | operations = [ 18 | migrations.CreateModel( 19 | name='Profile', 20 | fields=[ 21 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 22 | ('image', models.ImageField(default='avatar.png', upload_to=users.models.user_profile_path)), 23 | ('bio', models.TextField(blank=True)), 24 | ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 25 | ], 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Enes Turan 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 | -------------------------------------------------------------------------------- /src/cblog/urls.py: -------------------------------------------------------------------------------- 1 | """cblog URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/3.1/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.conf import settings 17 | from django.conf.urls.static import static 18 | from django.contrib import admin 19 | from django.urls import path, include 20 | 21 | urlpatterns = [ 22 | path('admin/', admin.site.urls), 23 | path("", include("blog.urls")), 24 | path("users/", include("users.urls")) 25 | ] 26 | 27 | 28 | if settings.DEBUG: 29 | urlpatterns += static(settings.MEDIA_URL, 30 | document_root=settings.MEDIA_ROOT) 31 | -------------------------------------------------------------------------------- /src/users/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from django.contrib.auth import views as auth_views 3 | from .views import register, profile 4 | from .forms import PasswordResetEmailCheck 5 | 6 | urlpatterns = [ 7 | path("register/", register, name="register"), 8 | path("profile/", profile, name="profile"), 9 | path("login/", auth_views.LoginView.as_view(template_name="users/login.html"), name="login"), 10 | path("logout/", auth_views.LogoutView.as_view(template_name="users/logout.html"), name="logout"), 11 | path('password-reset/', auth_views.PasswordResetView.as_view( 12 | template_name='users/password_reset_email.html', form_class=PasswordResetEmailCheck), name='password_reset'), 13 | path('password-reset/done/', auth_views.PasswordResetDoneView.as_view( 14 | template_name='users/password_reset_done.html'), name='password_reset_done'), 15 | path('password-reset-confirm//', auth_views.PasswordResetConfirmView.as_view( 16 | template_name='users/password_reset_confirm.html'), name='password_reset_confirm'), 17 | path('password-reset-complete/', auth_views.PasswordResetCompleteView.as_view( 18 | template_name='users/password_reset_complete.html'), name='password_reset_complete'), 19 | ] 20 | -------------------------------------------------------------------------------- /src/blog/templates/blog/post_list.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 |

Enes Blog

5 |
6 | {% for obj in object_list %} 7 |
8 | 9 |
10 | post_image 11 |
12 |
13 |
{{obj.title}}
14 |

{{obj.content|truncatechars:20}}

15 |

16 | {{ obj.comment_count }} 17 | {{ obj.view_count }} 18 | {{ obj.like_count }} 19 |

20 | Posted {{ obj.publish_date|timesince }} ago. 21 | 22 |

23 | 24 |

25 |
26 |
27 |
28 | 29 | {% endfor %} 30 |
31 | {% endblock content %} -------------------------------------------------------------------------------- /src/users/views.py: -------------------------------------------------------------------------------- 1 | from django.contrib import messages 2 | from django.shortcuts import redirect, render 3 | from .forms import RegistrationForm, UserUpdateForm, ProfileUpdateForm 4 | 5 | 6 | def register(request): 7 | form = RegistrationForm(request.POST or None) 8 | if request.user.is_authenticated: 9 | messages.warning(request, "You already have an account!") 10 | return redirect("blog:list") 11 | if form.is_valid(): 12 | form.save() 13 | name = form.cleaned_data["username"] 14 | messages.success(request, f"Acoount created for {name}") 15 | return redirect("login") 16 | 17 | context = { 18 | "form": form, 19 | } 20 | 21 | return render(request, "users/register.html", context) 22 | 23 | 24 | def profile(request): 25 | # obj = User.objects.get(id=id) 26 | u_form = UserUpdateForm(request.POST or None, instance=request.user) 27 | p_form = ProfileUpdateForm( 28 | request.POST or None, request.FILES or None, instance=request.user.profile) 29 | 30 | if u_form.is_valid() and p_form.is_valid(): 31 | u_form.save() 32 | p_form.save() 33 | messages.success(request, "Your profile has been updated!!") 34 | return redirect(request.path) 35 | 36 | context = { 37 | "u_form": u_form, 38 | "p_form": p_form 39 | } 40 | 41 | return render(request, "users/profile.html", context) 42 | -------------------------------------------------------------------------------- /developer_notes.md: -------------------------------------------------------------------------------- 1 | ## Hi DevOps Team; 2 | 3 | - We have coded a blog app. Users can publish their blog pages which have their comments, movie or picture files. 4 | 5 | - Movie and picture files are kept in S3 as object. 6 | 7 | - You should create an S3 bucket and write name of it on "/src/cblog/settings.py" file as AWS_STORAGE_BUCKET_NAME variable. 8 | 9 | - In addition, you must assign region of S3 as AWS_S3_REGION_NAME variable. 10 | 11 | - Users credentials and blog contents are going to be kept on RDS database. To connect ECs to RDS, following variables must be assigned on "/src/cblog/settings.py" file after you create RDS; 12 | 13 | - a. Database name - "NAME" variable 14 | 15 | - b. Database endpoint - "HOST" variables 16 | 17 | - c. Port - "PORT" 18 | 19 | - d. PASSWORD variable must be written on "/src/.env" file not to be exposed with settings file 20 | 21 | - We need to look object list of S3. 22 | 23 | - That's why, we decided to create a DynamoDB table. We have thought to use Lambda function for this purpose and also we have written python code. 24 | - However, we need help to create Lambda function to manage this serverless process. You can find our python function as lambda_function.py within github repo. 25 | 26 | - Since our first aim is to keep our environment in highly secure environment, we want you to establish this infrastructure in our own VPC. 27 | 28 | Thanks for your time and patience. Good Luck! 29 | -------------------------------------------------------------------------------- /src/users/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.contrib.auth.models import User 3 | from django.contrib.auth.forms import UserCreationForm, PasswordResetForm 4 | from django.forms import fields 5 | from .models import Profile 6 | 7 | 8 | class RegistrationForm(UserCreationForm): 9 | email = forms.EmailField() 10 | 11 | class Meta: 12 | model = User 13 | fields = ("username", "email") 14 | 15 | def clean_email(self): 16 | email = self.cleaned_data['email'] # HENRY@GMAİL.COM 17 | if User.objects.filter(email=email).exists(): 18 | raise forms.ValidationError( 19 | "Please use another Email, that one already taken") 20 | return email 21 | 22 | # def clean_first_name(self): 23 | # name = self.cleaned_data["first_name"] 24 | # if "a" in name: 25 | # raise forms.ValidationError("Your name includes A") 26 | # return name 27 | 28 | 29 | class ProfileUpdateForm(forms.ModelForm): 30 | class Meta: 31 | model = Profile 32 | fields = ("image", "bio") 33 | 34 | 35 | class UserUpdateForm(forms.ModelForm): 36 | class Meta: 37 | model = User 38 | fields = ("username", "email") 39 | 40 | 41 | class PasswordResetEmailCheck(PasswordResetForm): 42 | 43 | def clean_email(self): 44 | email = self.cleaned_data["email"] 45 | if not User.objects.filter(email=email).exists(): 46 | raise forms.ValidationError("There is no email") 47 | return email 48 | -------------------------------------------------------------------------------- /src/templates/navbar.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/templates/base.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 13 | 14 | 15 | DJ Blog 16 | 17 | 18 | 19 | {% include 'navbar.html' %} 20 |
21 | {% if messages %} 22 | {% for message in messages %} 23 |
24 | {{message}} 25 |
26 | 27 |
28 | {% endfor %} 29 | {% endif %} 30 |
31 | {% block content %} 32 | 33 | {% endblock content %} 34 |
35 | 36 | 37 | 40 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/blog/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.auth.models import User 3 | 4 | 5 | def user_directory_path(instance, filename): 6 | return 'blog/{0}/{1}'.format(instance.author.id, filename) 7 | 8 | 9 | # class Category(models.Model): 10 | # name = models.CharField(max_length=100) 11 | 12 | # class Meta: 13 | # verbose_name_plural = "Categories" 14 | 15 | # def __str__(self): 16 | # return self.name 17 | 18 | 19 | class Post(models.Model): 20 | OPTIONS = ( 21 | ('d', 'Draft'), 22 | ('p', 'Published') 23 | ) 24 | CATEGORY_OPT = ( 25 | ('e', 'Entertainment'), 26 | ('m', 'Music'), 27 | ('i', 'IT') 28 | ) 29 | title = models.CharField(max_length=100) 30 | content = models.TextField() 31 | image = models.ImageField( 32 | upload_to=user_directory_path, default='django.jpg') 33 | category = models.CharField(max_length=15, choices=CATEGORY_OPT, default='e') 34 | publish_date = models.DateTimeField(auto_now_add=True) 35 | last_updated = models.DateTimeField(auto_now=True) 36 | author = models.ForeignKey(User, on_delete=models.CASCADE) 37 | status = models.CharField(max_length=10, choices=OPTIONS, default='d') 38 | slug = models.SlugField(blank=True, unique=True) # how-to-learn-django 39 | 40 | def __str__(self): 41 | return self.title 42 | 43 | def comment_count(self): 44 | return self.comment_set.all().count() 45 | 46 | def view_count(self): 47 | return self.postview_set.all().count() 48 | 49 | def like_count(self): 50 | return self.like_set.all().count() 51 | 52 | def comments(self): 53 | return self.comment_set.all() 54 | 55 | 56 | class Comment(models.Model): 57 | user = models.ForeignKey(User, on_delete=models.CASCADE) 58 | post = models.ForeignKey(Post, on_delete=models.CASCADE) 59 | time_stamp = models.DateTimeField(auto_now_add=True) 60 | content = models.TextField() 61 | 62 | def __str__(self): 63 | return self.user.username 64 | 65 | 66 | class Like(models.Model): 67 | user = models.ForeignKey(User, on_delete=models.CASCADE) 68 | post = models.ForeignKey(Post, on_delete=models.CASCADE) 69 | 70 | def __str__(self): 71 | return self.user.username 72 | 73 | 74 | class PostView(models.Model): 75 | user = models.ForeignKey(User, on_delete=models.CASCADE) 76 | post = models.ForeignKey(Post, on_delete=models.CASCADE) 77 | time_stamp = models.DateTimeField(auto_now_add=True) 78 | 79 | def __str__(self): 80 | return self.user.username 81 | -------------------------------------------------------------------------------- /src/blog/templates/blog/post_detail.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load crispy_forms_tags %} 3 | {% block content %} 4 |
5 | post_image 6 |
7 |

{{ object.title }}

8 |
9 |
10 | {{ object.comment_count }} 11 | {{ object.view_count }} 12 | {{ object.like_count }} 13 | Posted {{ object.publish_date|timesince }} ago. 14 |
15 |
16 |

{{ object.content }}.

17 |
18 |
19 |

Enjoy this post? Give it a LIKE!!

20 |
21 | 22 |
23 |
24 | 25 | {% csrf_token %} 26 | 27 | 28 | 29 | 30 | 31 | {{object.like_count }} 32 |
33 |
34 | 35 |

Leave a comment below

36 |
37 | {% csrf_token %} 38 | {{form|crispy}} 39 | 40 |
41 |
42 |

Comments

43 | {% for comment in object.comments %} 44 |
45 |

46 | Comment by {{user.username}} - {{ comment.time_stamp|timesince }} ago. 47 | 48 |

49 |

50 | {{ comment.content }} 51 | 52 |

53 |
54 |
55 | {% endfor %} 56 | 57 | 58 | 59 |
60 | 61 |
62 | 63 |
64 | {% if user.id == object.author.id %} 65 | Edit 66 | Delete 67 | {% endif %} 68 |
69 | {% endblock content %} -------------------------------------------------------------------------------- /src/blog/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.4 on 2021-02-28 14:30 2 | 3 | import blog.models 4 | from django.conf import settings 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | initial = True 12 | 13 | dependencies = [ 14 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 15 | ] 16 | 17 | operations = [ 18 | migrations.CreateModel( 19 | name='Category', 20 | fields=[ 21 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 22 | ('name', models.CharField(max_length=100)), 23 | ], 24 | options={ 25 | 'verbose_name_plural': 'Categories', 26 | }, 27 | ), 28 | migrations.CreateModel( 29 | name='Post', 30 | fields=[ 31 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 32 | ('title', models.CharField(max_length=100)), 33 | ('content', models.TextField()), 34 | ('image', models.ImageField(default='django.jpg', upload_to=blog.models.user_directory_path)), 35 | ('publish_date', models.DateTimeField(auto_now_add=True)), 36 | ('last_updated', models.DateTimeField(auto_now=True)), 37 | ('status', models.CharField(choices=[('d', 'Draft'), ('p', 'Published')], default='d', max_length=10)), 38 | ('slug', models.SlugField(blank=True, unique=True)), 39 | ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 40 | ('category', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='cats', to='blog.category')), 41 | ], 42 | ), 43 | migrations.CreateModel( 44 | name='PostView', 45 | fields=[ 46 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 47 | ('time_stamp', models.DateTimeField(auto_now_add=True)), 48 | ('post', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.post')), 49 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 50 | ], 51 | ), 52 | migrations.CreateModel( 53 | name='Like', 54 | fields=[ 55 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 56 | ('post', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.post')), 57 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 58 | ], 59 | ), 60 | migrations.CreateModel( 61 | name='Comment', 62 | fields=[ 63 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 64 | ('time_stamp', models.DateTimeField(auto_now_add=True)), 65 | ('content', models.TextField()), 66 | ('post', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.post')), 67 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 68 | ], 69 | ), 70 | ] 71 | -------------------------------------------------------------------------------- /src/blog/views.py: -------------------------------------------------------------------------------- 1 | # from django.http.response import HttpResponse 2 | from django.shortcuts import get_object_or_404, redirect, render 3 | from .models import Post, Like, PostView 4 | from .forms import CommentForm, PostForm 5 | from django.contrib.auth.decorators import login_required 6 | from django.contrib import messages 7 | 8 | 9 | def post_list(request): 10 | qs = Post.objects.filter(status='p') 11 | context = { 12 | "object_list": qs 13 | } 14 | return render(request, "blog/post_list.html", context) 15 | 16 | 17 | @login_required() 18 | def post_create(request): 19 | # form = PostForm(request.POST or None, request.FILES or None) 20 | 21 | form = PostForm() 22 | if request.method == "POST": 23 | form = PostForm(request.POST, request.FILES) 24 | if form.is_valid(): 25 | post = form.save(commit=False) 26 | post.author = request.user 27 | post.save() 28 | messages.success(request, "Post created succesfully!") 29 | return redirect("blog:list") 30 | context = { 31 | 'form': form 32 | } 33 | return render(request, "blog/post_create.html", context) 34 | 35 | 36 | def post_detail(request, slug): 37 | # print(request.get_host()) 38 | # print(slug) 39 | # Post.objects.get(slug=learn-drf-3c78be2186) 40 | # slug = learn-drf-3c78be2186 41 | form = CommentForm() 42 | obj = get_object_or_404(Post, slug=slug) 43 | if request.user.is_authenticated: 44 | PostView.objects.create(user=request.user, post=obj) 45 | if request.method == "POST": 46 | form = CommentForm(request.POST) 47 | if form.is_valid: 48 | comment = form.save(commit=False) 49 | comment.user = request.user 50 | comment.post = obj 51 | comment.save() 52 | return redirect("blog:detail", slug=slug) 53 | # return redirect(request.path) 54 | context = { 55 | "object": obj, 56 | "form": form 57 | } 58 | return render(request, "blog/post_detail.html", context) 59 | 60 | 61 | @login_required() 62 | def post_update(request, slug): 63 | obj = get_object_or_404(Post, slug=slug) 64 | form = PostForm(request.POST or None, request.FILES or None, instance=obj) 65 | if request.user != obj.author: 66 | messages.warning(request, "You're not a writer of this post") 67 | return redirect('blog:list') 68 | if form.is_valid(): 69 | form.save() 70 | messages.success(request, "Post updated!!") 71 | return redirect("blog:list") 72 | 73 | context = { 74 | "object": obj, 75 | "form": form 76 | } 77 | return render(request, "blog/post_update.html", context) 78 | 79 | 80 | @login_required() 81 | def post_delete(request, slug): 82 | obj = get_object_or_404(Post, slug=slug) 83 | 84 | if request.user.id != obj.author.id: 85 | messages.warning(request, "You're not a writer of this post") 86 | return redirect('blog:list') 87 | if request.method == "POST": 88 | obj.delete() 89 | messages.success(request, "Post deleted!!") 90 | return redirect("blog:list") 91 | context = { 92 | "object": obj 93 | } 94 | return render(request, "blog/post_delete.html", context) 95 | 96 | 97 | @login_required() 98 | def like(request, slug): 99 | if request.method == "POST": 100 | obj = get_object_or_404(Post, slug=slug) 101 | like_qs = Like.objects.filter(user=request.user, post=obj) 102 | if like_qs: 103 | like_qs.delete() 104 | else: 105 | Like.objects.create(user=request.user, post=obj) 106 | return redirect('blog:detail', slug=slug) 107 | return redirect('blog:detail', slug=slug) 108 | -------------------------------------------------------------------------------- /src/cblog/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for cblog project. 3 | 4 | Generated by 'django-admin startproject' using Django 3.1.4. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.1/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/3.1/ref/settings/ 11 | """ 12 | 13 | from pathlib import Path 14 | from decouple import config 15 | import os 16 | 17 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 18 | BASE_DIR = Path(__file__).resolve().parent.parent 19 | 20 | 21 | # Quick-start development settings - unsuitable for production 22 | # See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/ 23 | 24 | # SECURITY WARNING: keep the secret key used in production secret! 25 | SECRET_KEY = config('SECRET_KEY') 26 | 27 | # SECURITY WARNING: don't run with debug turned on in production! 28 | DEBUG = False 29 | 30 | ALLOWED_HOSTS = ['*'] 31 | 32 | 33 | # Application definition 34 | 35 | INSTALLED_APPS = [ 36 | 'django.contrib.admin', 37 | 'django.contrib.auth', 38 | 'django.contrib.contenttypes', 39 | 'django.contrib.sessions', 40 | 'django.contrib.messages', 41 | 'django.contrib.staticfiles', 42 | # my_apps 43 | 'blog.apps.BlogConfig', 44 | 'users.apps.UsersConfig', 45 | 46 | # third party 47 | 'crispy_forms', 48 | 'storages' 49 | ] 50 | 51 | MIDDLEWARE = [ 52 | 'django.middleware.security.SecurityMiddleware', 53 | 'django.contrib.sessions.middleware.SessionMiddleware', 54 | 'django.middleware.common.CommonMiddleware', 55 | 'django.middleware.csrf.CsrfViewMiddleware', 56 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 57 | 'django.contrib.messages.middleware.MessageMiddleware', 58 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 59 | ] 60 | 61 | ROOT_URLCONF = 'cblog.urls' 62 | 63 | TEMPLATES = [ 64 | { 65 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 66 | 'DIRS': [BASE_DIR, "templates"], 67 | 'APP_DIRS': True, 68 | 'OPTIONS': { 69 | 'context_processors': [ 70 | 'django.template.context_processors.debug', 71 | 'django.template.context_processors.request', 72 | 'django.contrib.auth.context_processors.auth', 73 | 'django.contrib.messages.context_processors.messages', 74 | ], 75 | }, 76 | }, 77 | ] 78 | 79 | WSGI_APPLICATION = 'cblog.wsgi.application' 80 | 81 | 82 | # Database 83 | # https://docs.djangoproject.com/en/3.1/ref/settings/#databases 84 | 85 | DATABASES = { 86 | 'default': { 87 | 'ENGINE': 'django.db.backends.mysql', 88 | 'NAME': 'database1', # database name in RDS is written here 89 | 'USER': 'admin', # database master username in RDS is written here 90 | 'PASSWORD': config('PASSWORD'), 91 | # database endpoint is written here 92 | 'HOST': 'aws-capstone-rds.cukd79ofsohr.us-east-1.rds.amazonaws.com', 93 | 'PORT': '3306' # database port number is written here 94 | } 95 | } 96 | 97 | 98 | # Password validation 99 | # https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators 100 | 101 | AUTH_PASSWORD_VALIDATORS = [ 102 | { 103 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 104 | }, 105 | { 106 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 107 | }, 108 | { 109 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 110 | }, 111 | { 112 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 113 | }, 114 | ] 115 | 116 | 117 | # Internationalization 118 | # https://docs.djangoproject.com/en/3.1/topics/i18n/ 119 | 120 | LANGUAGE_CODE = 'en-us' 121 | 122 | TIME_ZONE = 'UTC' 123 | 124 | USE_I18N = True 125 | 126 | USE_L10N = True 127 | 128 | USE_TZ = True 129 | 130 | 131 | # Static files (CSS, JavaScript, Images) 132 | # https://docs.djangoproject.com/en/3.1/howto/static-files/ 133 | 134 | STATIC_URL = '/static/' 135 | MEDIA_URL = '/media/' 136 | STATICFILES_DIRS = [BASE_DIR / "static"] 137 | 138 | MEDIA_ROOT = BASE_DIR / "media_root" 139 | 140 | CRISPY_TEMPLATE_PACK = 'bootstrap4' 141 | 142 | LOGIN_REDIRECT_URL = "blog:list" 143 | 144 | LOGIN_URL = "login" 145 | 146 | 147 | AWS_STORAGE_BUCKET_NAME = 'enesblog' # please enter your s3 bucket name 148 | AWS_S3_CUSTOM_DOMAIN = '%s.s3.amazonaws.com' % AWS_STORAGE_BUCKET_NAME 149 | AWS_S3_REGION_NAME = "us-east-1" # please enter your s3 region 150 | AWS_DEFAULT_ACL = 'public-read' 151 | 152 | AWS_LOCATION = 'static' 153 | STATICFILES_DIRS = [ 154 | os.path.join(BASE_DIR, 'static'), 155 | ] 156 | 157 | STATIC_URL = 'https://%s/%s/' % (AWS_S3_CUSTOM_DOMAIN, AWS_LOCATION) 158 | STATICFILES_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage' 159 | DEFAULT_FILE_STORAGE = 'cblog.storages.MediaStore' 160 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Django Blog Page Application deployed on AWS Application Load Balancer with Auto Scaling, S3, Relational Database Service(RDS), VPC's Components, Lambda, DynamoDB and CloudFront with Route 53 2 | 3 | ## Description 4 | 5 | The Blog Page Application aims to deploy blog application as a web application written Django Framework on AWS Cloud Infrastructure. This infrastructure has Application Load Balancer with Auto Scaling Group of Elastic Compute Cloud (EC2) Instances and Relational Database Service (RDS) on defined VPC. Also, The CloudFront and Route 53 services are located in front of the architecture and manage the traffic in secure. User is able to upload pictures and videos on own blog page and these are kept on S3 Bucket. 6 | 7 | ## Project Details 8 | 9 | ![Project](project.jpg) 10 | 11 | - Your company has recently ended up a project that aims to serve as Blog web application on isolated VPC environment. You and your colleagues have started to work on the project. Your Developer team has developed the application and you are going to deploy the app in production environment. 12 | 13 | - Application is coded by Fullstack development team and given you as DevOps team. App allows users to write their own blog page to whom user registration data should be kept in separate MySQL database in AWS RDS service and pictures or videos should be kept in S3 bucket. The object list of S3 Bucket containing movies and videos is recorded on DynamoDB table. 14 | 15 | - The web application will be deployed using Django framework. 16 | 17 | - The Web Application should be accessible via web browser from anywhere in secure. 18 | 19 | - You are requested to push your program to the project repository on the Github. You are going to pull it into the webservers in the production environment on AWS Cloud. 20 | 21 | In the architecture, you can configure your infrastructure using the followings, 22 | 23 | - The application stack should be created with new AWS resources. 24 | 25 | - Specifications of VPC: 26 | 27 | - VPC has two AZs and every AZ has 1 public and 1 private subnets. 28 | 29 | - VPC has Internet Gateway 30 | 31 | - One of public subnets has NAT Instance. 32 | 33 | - You might create new instance as Bastion host on Public subnet or you can use NAT instance as Bastion host. 34 | 35 | - There should be managed private and public route tables. 36 | 37 | - Route tables should be arranged regarding of routing policies and subnet associations based on public and private subnets. 38 | 39 | - You should create Application Load Balancer with Auto Scaling Group of Ubuntu 18.04 EC2 Instances within created VPC. 40 | 41 | - You should create RDS instance within one of private subnets on created VPC and configure it on application. 42 | 43 | - The Auto Scaling Group should use a Launch Template in order to launch instances needed and should be configured to; 44 | 45 | - use all Availability Zones on created VPC. 46 | 47 | - set desired capacity of instances to ` 2` 48 | 49 | - set minimum size of instances to ` 2` 50 | 51 | - set maximum size of instances to ` 4` 52 | 53 | - set health check grace period to ` 90 seconds` 54 | 55 | - set health check type to ` ELB` 56 | 57 | - Scaling Policy --> Target Tracking Policy 58 | 59 | - Average CPU utilization (set Target Value ` %70`) 60 | 61 | - seconds warm up before including in metric ---> `200` 62 | 63 | - Set notification to your email address for launch, terminate, fail to launch, fail to terminate instance situations 64 | 65 | - ALB configuration; 66 | 67 | - Application Load Balancer should be placed within a security group which allows HTTP (80) and HTTPS (443) connections from anywhere. 68 | 69 | - Certification should be created for secure connection (HTTPS) 70 | 71 | - To create certificate, AWS Certificate Manager can be utilized. 72 | 73 | - ALB redirects to traffic from HTTP to HTTPS 74 | 75 | - Target Group 76 | - Health Check Protocol is going to be HTTP 77 | 78 | - The Launch Template should be configured to; 79 | 80 | - Prepare Django environment on EC2 instance based on Developer Notes, 81 | 82 | - Deploy the Django application on port 80. 83 | 84 | - Launch Template only allows HTTP (80) and HTTPS (443) ports coming from ALB Security Group and SSH (22) connections from anywhere. 85 | 86 | - EC2 Instances type can be configured as `t2.micro`. 87 | 88 | - Instance launched should be tagged `AWS Capstone Project` 89 | 90 | - Since Django App needs to talk with S3, S3 full access role must be attached EC2s. 91 | 92 | - For RDS Database Instance; 93 | 94 | - Instance type can be configured as `db.t2.micro` 95 | 96 | - Database engine can be `MySQL` with version of `8.0.20`. 97 | 98 | - RDS endpoint should be addressed within settings file of blog application that is explained developer notes. 99 | 100 | - Please read carefully "Developer notes" to manage RDS sub settings. 101 | 102 | - CloudFront should be set as a cache server which points to Application Load Balance with following configurations; 103 | 104 | - The CloudFront distribution should communicate with ALB securely. 105 | 106 | - Origin Protocol policy can be selected as `HTTPS only`. 107 | 108 | - Viewer Protocol Policy can be selected as `Redirect HTTP to HTTPS` 109 | 110 | - As cache behavior; 111 | 112 | - GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE methods should be allowed. 113 | 114 | - Forward Cookies must be selected All. 115 | 116 | - Newly created ACM Certificate should be used for securing connections. (You can use same certificate with ALB) 117 | 118 | - Route 53 119 | 120 | - Connection must be secure (HTTPS). 121 | 122 | - Your hostname can be used to publish website. 123 | 124 | - Failover routing policy should be set while publishing application 125 | 126 | - Primary connection is going to be CloudFormation 127 | 128 | - Secondary connection is going to be a static website placed another S3 bucket. This S3 bucket has just basic static website that has a picture said "the page is under construction" given files within S3_static_Website folder 129 | 130 | - Healthcheck should check If CloudFront is healthy or not. 131 | 132 | - As S3 Bucket 133 | 134 | - First S3 Bucket 135 | 136 | - It should be created within the Region that you created VPC 137 | 138 | - Since development team doesn't prefer to expose traffic between S3 and EC2s on internet, Endpoint should be set on created VPC. 139 | 140 | - S3 Bucket name should be addressed within configuration file of blog application that is explained developer notes. 141 | 142 | - Second S3 Bucket 143 | 144 | - This Bucket is going to be used for failover scenario. It has just a basic static website that has a picture said "the page is under construction" 145 | 146 | - To write the objects of S3 on DynamoDB table 147 | 148 | - Lambda Function 149 | 150 | - Lambda function is going to be Python 3.8 151 | 152 | - Python Function can be found in github repo 153 | 154 | - S3 event is set as trigger 155 | 156 | - Since Lambda needs to talk S3 and DynamoDB and to run on created VPC, S3, DynamoDB full access policies and NetworkAdministrator policy must be attached it 157 | 158 | - `S3 Event` must be created first S3 Bucket to trigger Lambda function 159 | 160 | - DynamoDB Table 161 | 162 | - Create a DynamoDB table which has primary key that is `id` 163 | 164 | - Created DynamoDB table's name should be placed on Lambda function. 165 | 166 | ## Expected Outcome 167 | 168 | ![Phonebook App Search Page](./outcome.png) 169 | 170 | ### The following topics will be used at the end of the project: 171 | 172 | - Bash scripting 173 | 174 | - AWS EC2 Launch Template Configuration 175 | 176 | - AWS VPC Configuration 177 | 178 | - VPC 179 | - Private and Public Subnets 180 | - Private and Public Route Tables 181 | - Managing routes 182 | - Subnet Associations 183 | - Internet Gateway 184 | - NAT Gateway 185 | - Bastion Host 186 | - Endpoint 187 | 188 | - AWS EC2 Application Load Balancer Configuration 189 | 190 | - AWS EC2 ALB Target Group Configuration 191 | 192 | - AWS EC2 ALB Listener Configuration 193 | 194 | - AWS EC2 Auto Scaling Group Configuration 195 | 196 | - AWS Relational Database Service Configuration 197 | 198 | - AWS EC2, RDS, ALB Security Groups Configuration 199 | 200 | - IAM Roles configuration 201 | 202 | - S3 configuration 203 | 204 | - Static website configuration on S3 205 | 206 | - DynamoDB Table configuration 207 | 208 | - Lambda Function configuration 209 | 210 | - Get Certificate with AWS Certification Manager Configuration 211 | 212 | - AWS CloudFront Configuration 213 | 214 | - Route 53 Configuration 215 | 216 | - Git & Github for Version Control System 217 | 218 | ### At the end of the project, you will be able to; 219 | 220 | - Construct VPC environment with whole components like public and private subnets, route tables and managing their routes, internet Gateway, NAT Instance. 221 | 222 | - Apply web programming skills, importing packages within Python Django Framework 223 | 224 | - Configure connection to the `MySQL` database. 225 | 226 | - Demonstrate bash scripting skills using `user data` section within launch template to install and setup Blog web application on EC2 Instance. 227 | 228 | - Create a Lambda function using S3, Lambda and DynamoDB table. 229 | 230 | - Demonstrate their configuration skills of AWS VPC, EC2 Launch Templates, Application Load Balancer, ALB Target Group, ALB Listener, Auto Scaling Group, S3, RDS, CloudFront, Route 53. 231 | 232 | - Apply git commands (push, pull, commit, add etc.) and Github as Version Control System. 233 | 234 | ## Solution Steps 235 | 236 | - Step 1: Create dedicated VPC and whole components 237 | 238 | - Step 2: Create Security Groups (ALB ---> EC2 ---> RDS) 239 | 240 | - Step 3: Create RDS 241 | 242 | - Step 4: Create two S3 Buckets and set one of these as static website 243 | 244 | - Step 5: Download or clone project definition 245 | 246 | - Step 6: Prepare your Github repository 247 | 248 | - Step 7: Prepare a userdata to be utilized in Launch Template 249 | 250 | - Step 8: Write RDS, S3 in settings file given by Fullstack Developer team 251 | 252 | - Step 9: Create NAT Instance in Public Subnet 253 | 254 | - Step 10: Create Launch Template and IAM role for it 255 | 256 | - Step 11: Create certification for secure connection 257 | 258 | - Step 12: Create ALB and Target Group 259 | 260 | - Step 13: Create Autoscaling Group with Launch Template 261 | 262 | - Step 14: Create CloudFront in front of ALB 263 | 264 | - Step 15: Create Route 53 with Failover settings 265 | 266 | - Step 16: Create DynamoDB Table 267 | 268 | - Step 17-18: Create Lambda function 269 | 270 | - Step 17-18: Create S3 Event and set it as trigger for Lambda Function 271 | 272 | ## Notes 273 | 274 | - RDS database should be located in private subnet. just EC2 machines that has ALB security group can talk with RDS. 275 | 276 | - RDS is located on private groups and only EC2s can talk with it on port 3306 277 | 278 | - ALB is located public subnet and it redirects traffic from http to https 279 | 280 | - EC2's are located in private subnets and only ALB can talk with them 281 | 282 | ## Resources 283 | 284 | - [Python Django Framework](https://www.djangoproject.com/) 285 | 286 | - [Python Django Example](https://realpython.com/get-started-with-django-1/) 287 | 288 | - [AWS CLI Command Reference](https://docs.aws.amazon.com/cli/latest/index.html) 289 | --------------------------------------------------------------------------------