├── .gitignore ├── README.md ├── project0 ├── advanced_search.html ├── geegle.png ├── image_search.html ├── index.html └── styles.css ├── project1 ├── encyclopedia │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── forms.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── static │ │ └── encyclopedia │ │ │ └── styles.css │ ├── templates │ │ └── encyclopedia │ │ │ ├── article.html │ │ │ ├── edit.html │ │ │ ├── error.html │ │ │ ├── index.html │ │ │ ├── layout.html │ │ │ ├── new_page.html │ │ │ └── search.html │ ├── tests.py │ ├── urls.py │ ├── util.py │ └── views.py ├── entries │ ├── CSS.md │ ├── Django.md │ ├── Git.md │ ├── HTML.md │ └── Python.md ├── manage.py └── wiki │ ├── __init__.py │ ├── asgi.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── project2 ├── auctions │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── forms.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_auto_20200920_1520.py │ │ ├── 0003_favorites.py │ │ ├── 0004_auto_20200923_1101.py │ │ ├── 0005_bid_amount.py │ │ ├── 0006_auto_20200924_2155.py │ │ ├── 0007_auto_20200926_0002.py │ │ ├── 0008_comment.py │ │ ├── 0009_comment_body.py │ │ ├── 0010_bookmark.py │ │ ├── 0011_auto_20200926_1433.py │ │ ├── 0012_delete_bookmark.py │ │ ├── 0013_category_slug.py │ │ ├── 0014_auto_20200926_1835.py │ │ └── __init__.py │ ├── models.py │ ├── static │ │ └── auctions │ │ │ ├── css │ │ │ ├── materialize.css │ │ │ ├── materialize.min.css │ │ │ └── styles.css │ │ │ └── js │ │ │ ├── init.js │ │ │ ├── materialize.js │ │ │ └── materialize.min.js │ ├── templates │ │ └── auctions │ │ │ ├── auction_view.html │ │ │ ├── bookmarks.html │ │ │ ├── categories.html │ │ │ ├── category_listings.html │ │ │ ├── includes │ │ │ ├── footer.html │ │ │ └── header.html │ │ │ ├── index.html │ │ │ ├── layout.html │ │ │ ├── login.html │ │ │ ├── new_auction.html │ │ │ └── register.html │ ├── tests.py │ ├── urls.py │ └── views.py ├── commerce │ ├── __init__.py │ ├── asgi.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py └── manage.py ├── project3 ├── mail │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── static │ │ └── mail │ │ │ ├── inbox.js │ │ │ └── styles.css │ ├── templates │ │ └── mail │ │ │ ├── inbox.html │ │ │ ├── layout.html │ │ │ ├── login.html │ │ │ └── register.html │ ├── tests.py │ ├── urls.py │ └── views.py ├── manage.py └── project3 │ ├── __init__.py │ ├── asgi.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py └── project4 ├── manage.py ├── network ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_post.py │ ├── 0003_alter_post_options.py │ ├── 0004_alter_post_options.py │ ├── 0005_user_followers.py │ ├── 0006_auto_20210920_2224.py │ └── __init__.py ├── models.py ├── static │ └── network │ │ ├── index-page.js │ │ └── styles.css ├── templates │ └── network │ │ ├── following.html │ │ ├── index.html │ │ ├── layout.html │ │ ├── login.html │ │ ├── register.html │ │ └── user_view.html ├── tests.py ├── urls.py └── views.py └── project4 ├── __init__.py ├── asgi.py ├── settings.py ├── urls.py └── wsgi.py /.gitignore: -------------------------------------------------------------------------------- 1 | **/venv 2 | **/.idea 3 | **/__pycache__ 4 | *.sqlite3 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CS50 Web Programming with Python and JavaScript 2 | Mine solutions for CS50's Web Programming with Python and JavaScript course. 3 | 4 | *Warning : before visiting this repo files, please read about [CS50's Academic Honesty rules](https://cs50.harvard.edu/college/2021/fall/syllabus/#academic-honesty)*. 5 | 6 | ## Includes: 7 | * Projects solutions 8 | 9 | ## Course info: 10 | * __Name:__ CS50's Web Programming with Python and JavaScript 11 | * __University:__ Harvard University 12 | * __WWW:__ https://cs50.harvard.edu/web/2020 13 | 14 | 15 | -------------------------------------------------------------------------------- /project0/advanced_search.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Advanced search 9 | 10 | 11 |
12 |
13 |
14 | 17 |
18 |
19 | 20 |
21 |
22 | 25 |
26 |
27 |

Search for:

28 |
29 | 30 |
31 |
32 | all these words: 33 |
34 |
35 | 36 |
37 |
38 | 39 |
40 |
41 | this exact word or phrase: 42 |
43 |
44 | 45 |
46 |
47 | 48 |
49 |
50 | any of these words: 51 |
52 |
53 | 54 |
55 |
56 | 57 |
58 |
59 | none of these words: 60 |
61 |
62 | 63 |
64 |
65 | 66 |
67 |
68 | 69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /project0/geegle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbsth/cs50w/90b58e0342ac3813fe0ee97b3191bd22836901fe/project0/geegle.png -------------------------------------------------------------------------------- /project0/image_search.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Image search 9 | 10 | 11 |
12 |
13 |
14 | 17 |
18 |
19 | 20 |
21 |
22 | 23 |
24 | Geegle web search 25 |
26 | 27 |
28 |
29 |
30 | 31 | 32 |
33 | 34 |
35 |
36 |
37 |
38 |
39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /project0/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Search 9 | 10 | 11 |
12 |
13 |
14 | 18 |
19 |
20 | 21 |
22 |
23 | 24 |
25 | Geegle web search 26 |
27 | 28 |
29 |
30 |
31 | 32 |
33 | 34 | 35 | 36 |
37 |
38 |
39 |
40 |
41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /project0/styles.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | height: 75%; 3 | } 4 | 5 | .container { 6 | height: 100%; 7 | } 8 | 9 | .form-rounded{ 10 | border-radius: 1rem; 11 | } 12 | 13 | .nav-link{ 14 | font-size: 0.9rem; 15 | } 16 | 17 | .btn-main{ 18 | color: rgb(95, 99, 104); 19 | background-color: rgb(242, 242, 242); 20 | border-color: rgb(242, 242, 242); 21 | } 22 | 23 | .btn-main:hover{ 24 | background-color: rgb(248, 248, 248); 25 | border-color: rgb(198, 198, 198); 26 | color: rgb(34, 34, 34); 27 | } 28 | 29 | #adv-search{ 30 | color: rgb(217, 48, 37); 31 | font-size: 1.3rem; 32 | } 33 | 34 | #search-text{ 35 | font-size: 1.2rem; 36 | } 37 | 38 | -------------------------------------------------------------------------------- /project1/encyclopedia/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbsth/cs50w/90b58e0342ac3813fe0ee97b3191bd22836901fe/project1/encyclopedia/__init__.py -------------------------------------------------------------------------------- /project1/encyclopedia/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /project1/encyclopedia/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class EncyclopediaConfig(AppConfig): 5 | name = 'encyclopedia' 6 | -------------------------------------------------------------------------------- /project1/encyclopedia/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | 3 | 4 | class ArticleForm(forms.Form): 5 | article_name = forms.CharField(label="Article name", max_length=20) 6 | #article_name.disabled = True 7 | article_content = forms.CharField( 8 | label="Article content", 9 | widget=forms.Textarea(attrs={'rows': 1, 'cols': 1}) 10 | ) 11 | 12 | 13 | class EditForm(forms.Form): 14 | article_content = forms.CharField( 15 | label="Article content", 16 | widget=forms.Textarea() 17 | ) 18 | -------------------------------------------------------------------------------- /project1/encyclopedia/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbsth/cs50w/90b58e0342ac3813fe0ee97b3191bd22836901fe/project1/encyclopedia/migrations/__init__.py -------------------------------------------------------------------------------- /project1/encyclopedia/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /project1/encyclopedia/static/encyclopedia/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | background-color: white; 4 | } 5 | 6 | code { 7 | white-space: pre; 8 | } 9 | 10 | h1 { 11 | margin-top: 0px; 12 | padding-top: 20px; 13 | } 14 | 15 | textarea { 16 | height: 90vh; 17 | width: 80%; 18 | } 19 | 20 | .main { 21 | padding: 10px; 22 | } 23 | 24 | .search { 25 | width: 100%; 26 | font-size: 15px; 27 | line-height: 15px; 28 | } 29 | 30 | .sidebar { 31 | background-color: #f0f0f0; 32 | height: 100vh; 33 | padding: 20px; 34 | } 35 | 36 | .sidebar h2 { 37 | margin-top: 5px; 38 | } -------------------------------------------------------------------------------- /project1/encyclopedia/templates/encyclopedia/article.html: -------------------------------------------------------------------------------- 1 | {% extends "encyclopedia/layout.html" %} 2 | 3 | {% block title %} 4 | {{ name }} 5 | {% endblock title %} 6 | 7 | {% block body %} 8 | edit 9 | {{ markdown | safe}} 10 | {% endblock body %} -------------------------------------------------------------------------------- /project1/encyclopedia/templates/encyclopedia/edit.html: -------------------------------------------------------------------------------- 1 | {% extends 'encyclopedia/layout.html' %} 2 | 3 | {% block title %} 4 | Edit page 5 | {% endblock title %} 6 | 7 | {% block body %} 8 |

Edit article

9 |
10 | {% csrf_token %} 11 |

Article name:

12 | {{ name }} 13 |

Article content:

14 | {{ form.article_content }} 15 | 16 |
17 | {% endblock body %} -------------------------------------------------------------------------------- /project1/encyclopedia/templates/encyclopedia/error.html: -------------------------------------------------------------------------------- 1 | {% extends "encyclopedia/layout.html" %} 2 | 3 | {% block title %} 4 | ERROR 5 | {% endblock title %} 6 | 7 | {% block body %} 8 |

9 | {{ error }} 10 |

11 | {% endblock body %} -------------------------------------------------------------------------------- /project1/encyclopedia/templates/encyclopedia/index.html: -------------------------------------------------------------------------------- 1 | {% extends "encyclopedia/layout.html" %} 2 | 3 | {% block title %} 4 | Encyclopedia 5 | {% endblock %} 6 | 7 | {% block body %} 8 |

All Pages

9 | 10 | 15 | 16 | {% endblock %} -------------------------------------------------------------------------------- /project1/encyclopedia/templates/encyclopedia/layout.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | 6 | 7 | {% block title %}{% endblock %} 8 | 9 | 10 | 11 | 12 |
13 | 30 |
31 | {% block body %} 32 | {% endblock %} 33 |
34 |
35 | 36 | 37 | -------------------------------------------------------------------------------- /project1/encyclopedia/templates/encyclopedia/new_page.html: -------------------------------------------------------------------------------- 1 | {% extends 'encyclopedia/layout.html' %} 2 | 3 | {% block title %} 4 | New page 5 | {% endblock title %} 6 | 7 | {% block body %} 8 |

Post new article

9 |
10 | {% csrf_token %} 11 |

Article name:

12 | {{ form.article_name }} 13 |

Article content:

14 | {{ form.article_content }} 15 | 16 |
17 | {% endblock body %} -------------------------------------------------------------------------------- /project1/encyclopedia/templates/encyclopedia/search.html: -------------------------------------------------------------------------------- 1 | {% extends 'encyclopedia/layout.html' %} 2 | 3 | {% block title %} 4 | Search 5 | {% endblock title %} 6 | 7 | {% block body %} 8 |

Search results:

9 | {% if results %} 10 | 15 | {% else %} 16 | Article not found 17 | {% endif %} 18 | {% endblock body %} -------------------------------------------------------------------------------- /project1/encyclopedia/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /project1/encyclopedia/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from . import views 4 | 5 | urlpatterns = [ 6 | path("", views.index, name="index"), 7 | path("wiki/", views.article, name="article"), 8 | path("search", views.search, name="search"), 9 | path("new", views.new_page, name="new_page"), 10 | path("random", views.random, name="random"), 11 | path("wiki//edit", views.edit, name="edit") 12 | ] 13 | -------------------------------------------------------------------------------- /project1/encyclopedia/util.py: -------------------------------------------------------------------------------- 1 | import re 2 | import markdown2 3 | 4 | from django.core.files.base import ContentFile 5 | from django.core.files.storage import default_storage 6 | 7 | 8 | def list_entries(): 9 | """ 10 | Returns a list of all names of encyclopedia entries. 11 | """ 12 | _, filenames = default_storage.listdir("entries") 13 | return list(sorted(re.sub(r"\.md$", "", filename) 14 | for filename in filenames if filename.endswith(".md"))) 15 | 16 | 17 | def save_entry(title, content): 18 | """ 19 | Saves an encyclopedia entry, given its title and Markdown 20 | content. If an existing entry with the same title already exists, 21 | it is replaced. 22 | """ 23 | filename = f"entries/{title}.md" 24 | if default_storage.exists(filename): 25 | default_storage.delete(filename) 26 | default_storage.save(filename, ContentFile(content)) 27 | 28 | 29 | def get_entry(title): 30 | """ 31 | Retrieves an encyclopedia entry by its title. If no such 32 | entry exists, the function returns None. 33 | """ 34 | try: 35 | f = default_storage.open(f"entries/{title}.md") 36 | return f.read().decode("utf-8") 37 | except FileNotFoundError: 38 | return None 39 | 40 | 41 | def convert_to_html(text): 42 | return markdown2.markdown(text) 43 | 44 | -------------------------------------------------------------------------------- /project1/encyclopedia/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render, redirect 2 | from django.core.files import File 3 | 4 | from random import choice 5 | 6 | from . import util 7 | from . import forms 8 | 9 | 10 | def index(request): 11 | return render(request, "encyclopedia/index.html", { 12 | "entries": util.list_entries() 13 | }) 14 | 15 | 16 | def article(request, name): 17 | if name.upper() in (name.upper() for name in util.list_entries()): 18 | return render(request, "encyclopedia/article.html", { 19 | "name": name, 20 | "markdown": util.convert_to_html(util.get_entry(name)) 21 | }) 22 | else: 23 | return render(request, "encyclopedia/error.html", { 24 | "error": 'no such article', 25 | }) 26 | 27 | 28 | def search(request): 29 | searched = request.GET['q'] 30 | if searched.upper() in (name.upper() for name in util.list_entries()): 31 | return redirect("article", name=searched) 32 | else: 33 | results = [i for i in util.list_entries() if searched.upper() in i.upper()] 34 | return render(request, "encyclopedia/search.html", { 35 | "results": results 36 | }) 37 | 38 | 39 | def new_page(request): 40 | if request.method == "POST": 41 | form = forms.ArticleForm(request.POST) 42 | if form.is_valid(): 43 | name = form.cleaned_data['article_name'] 44 | content = form.cleaned_data['article_content'] 45 | if name.upper() in (x.upper() for x in util.list_entries()): 46 | return render(request, 'encyclopedia/error.html', { 47 | "error": 'article already exist' 48 | }) 49 | else: 50 | with open(f'entries/{name}.md', 'w') as f: 51 | myfile = File(f) 52 | myfile.write(content) 53 | return redirect("article", name=name) 54 | return redirect("index") 55 | else: 56 | form = forms.ArticleForm() 57 | 58 | return render(request, "encyclopedia/new_page.html", { 59 | 'form': form 60 | }) 61 | 62 | 63 | def random(request): 64 | rand = choice(util.list_entries()) 65 | return redirect("article", name=rand) 66 | 67 | 68 | def edit(request, name): 69 | if request.method == "POST": 70 | form = forms.EditForm(request.POST) 71 | if form.is_valid(): 72 | new_content = form.cleaned_data['article_content'] 73 | with open(f'entries/{name}.md', 'w') as f: 74 | myfile = File(f) 75 | myfile.write(new_content) 76 | return redirect("article", name=name) 77 | else: 78 | existing_content = util.get_entry(name) 79 | form = forms.EditForm({ 80 | 'article_content': existing_content, 81 | }) 82 | # form.article_name = 'test' 83 | return render(request, 'encyclopedia/edit.html', { 84 | 'form': form, 85 | 'name': name 86 | }) -------------------------------------------------------------------------------- /project1/entries/CSS.md: -------------------------------------------------------------------------------- 1 | # CSS 2 | 3 | CSS is a language that can be used to add style to an [HTML](/wiki/HTML) page. 4 | -------------------------------------------------------------------------------- /project1/entries/Django.md: -------------------------------------------------------------------------------- 1 | # Django 2 | 3 | Django is a web framework written using [Python](/wiki/Python) that allows for the design of web applications that generate [HTML](/wiki/HTML) dynamically. 4 | -------------------------------------------------------------------------------- /project1/entries/Git.md: -------------------------------------------------------------------------------- 1 | # Git 2 | 3 | Git is a version control tool that can be used to keep track of versions of a software project. 4 | 5 | ## GitHub 6 | 7 | GitHub is an online service for hosting git repositories. 8 | -------------------------------------------------------------------------------- /project1/entries/HTML.md: -------------------------------------------------------------------------------- 1 | # HTML 2 | 3 | HTML is a markup language that can be used to define the structure of a web page. HTML elements include 4 | 5 | * headings 6 | * paragraphs 7 | * lists 8 | * links 9 | * and more! 10 | 11 | The most recent major version of HTML is HTML5. -------------------------------------------------------------------------------- /project1/entries/Python.md: -------------------------------------------------------------------------------- 1 | # Python 2 | 3 | Python is a programming language that can be used both for writing **command-line scripts** or building **web applications**. 4 | -------------------------------------------------------------------------------- /project1/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 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'wiki.settings') 9 | try: 10 | from django.core.management import execute_from_command_line 11 | except ImportError as exc: 12 | raise ImportError( 13 | "Couldn't import Django. Are you sure it's installed and " 14 | "available on your PYTHONPATH environment variable? Did you " 15 | "forget to activate a virtual environment?" 16 | ) from exc 17 | execute_from_command_line(sys.argv) 18 | 19 | 20 | if __name__ == '__main__': 21 | main() 22 | -------------------------------------------------------------------------------- /project1/wiki/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbsth/cs50w/90b58e0342ac3813fe0ee97b3191bd22836901fe/project1/wiki/__init__.py -------------------------------------------------------------------------------- /project1/wiki/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for wiki 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.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', 'wiki.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /project1/wiki/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for wiki project. 3 | 4 | Generated by 'django-admin startproject' using Django 3.0.2. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.0/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/3.0/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = '%710m*zic)#0u((qugw#1@e^ty!c)9j04956v@ly(_86n$rg)h' 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 | 'encyclopedia', 35 | 'django.contrib.admin', 36 | 'django.contrib.auth', 37 | 'django.contrib.contenttypes', 38 | 'django.contrib.sessions', 39 | 'django.contrib.messages', 40 | 'django.contrib.staticfiles', 41 | ] 42 | 43 | MIDDLEWARE = [ 44 | 'django.middleware.security.SecurityMiddleware', 45 | 'django.contrib.sessions.middleware.SessionMiddleware', 46 | 'django.middleware.common.CommonMiddleware', 47 | 'django.middleware.csrf.CsrfViewMiddleware', 48 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 49 | 'django.contrib.messages.middleware.MessageMiddleware', 50 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 51 | ] 52 | 53 | ROOT_URLCONF = 'wiki.urls' 54 | 55 | TEMPLATES = [ 56 | { 57 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 58 | 'DIRS': [], 59 | 'APP_DIRS': True, 60 | 'OPTIONS': { 61 | 'context_processors': [ 62 | 'django.template.context_processors.debug', 63 | 'django.template.context_processors.request', 64 | 'django.contrib.auth.context_processors.auth', 65 | 'django.contrib.messages.context_processors.messages', 66 | ], 67 | }, 68 | }, 69 | ] 70 | 71 | WSGI_APPLICATION = 'wiki.wsgi.application' 72 | 73 | 74 | # Database 75 | # https://docs.djangoproject.com/en/3.0/ref/settings/#databases 76 | 77 | DATABASES = { 78 | 'default': { 79 | 'ENGINE': 'django.db.backends.sqlite3', 80 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 81 | } 82 | } 83 | 84 | 85 | # Password validation 86 | # https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators 87 | 88 | AUTH_PASSWORD_VALIDATORS = [ 89 | { 90 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 91 | }, 92 | { 93 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 94 | }, 95 | { 96 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 97 | }, 98 | { 99 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 100 | }, 101 | ] 102 | 103 | 104 | # Internationalization 105 | # https://docs.djangoproject.com/en/3.0/topics/i18n/ 106 | 107 | LANGUAGE_CODE = 'en-us' 108 | 109 | TIME_ZONE = 'UTC' 110 | 111 | USE_I18N = True 112 | 113 | USE_L10N = True 114 | 115 | USE_TZ = True 116 | 117 | 118 | # Static files (CSS, JavaScript, Images) 119 | # https://docs.djangoproject.com/en/3.0/howto/static-files/ 120 | 121 | STATIC_URL = '/static/' 122 | -------------------------------------------------------------------------------- /project1/wiki/urls.py: -------------------------------------------------------------------------------- 1 | """wiki URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/3.0/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.contrib import admin 17 | from django.urls import include, path 18 | 19 | urlpatterns = [ 20 | path('admin/', admin.site.urls), 21 | path('', include("encyclopedia.urls")) 22 | ] 23 | -------------------------------------------------------------------------------- /project1/wiki/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for wiki 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.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', 'wiki.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /project2/auctions/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbsth/cs50w/90b58e0342ac3813fe0ee97b3191bd22836901fe/project2/auctions/__init__.py -------------------------------------------------------------------------------- /project2/auctions/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import * 3 | 4 | 5 | class AuctionAdmin(admin.ModelAdmin): 6 | list_display = ('title', 'category', 'starting_bid') 7 | 8 | 9 | admin.site.register(Category) 10 | admin.site.register(AuctionListening, AuctionAdmin) 11 | # admin.site.register(Favorites) 12 | admin.site.register(Bid) 13 | admin.site.register(User) 14 | admin.site.register(Comment) -------------------------------------------------------------------------------- /project2/auctions/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class AuctionsConfig(AppConfig): 5 | name = 'auctions' 6 | -------------------------------------------------------------------------------- /project2/auctions/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from auctions.models import AuctionListening, Category, Bid, Comment 3 | 4 | 5 | class NewAuctionForm(forms.ModelForm): 6 | class Meta: 7 | model = AuctionListening 8 | fields = ['title', 'description', 'category', 'image_url', 'starting_bid'] 9 | 10 | 11 | class BidForm(forms.ModelForm): 12 | def __init__(self, *args, **kwargs): 13 | self.auction = kwargs.pop('auction') 14 | super(BidForm, self).__init__(*args, **kwargs) 15 | 16 | def clean(self): 17 | amount = self.cleaned_data['amount'] 18 | if amount <= self.auction.current_price: 19 | raise forms.ValidationError("Your bid is lower than current price") 20 | 21 | return self.cleaned_data 22 | 23 | class Meta: 24 | model = Bid 25 | fields = ['amount'] 26 | 27 | 28 | class CommentForm(forms.ModelForm): 29 | class Meta: 30 | model = Comment 31 | fields = ['body'] 32 | 33 | -------------------------------------------------------------------------------- /project2/auctions/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.1 on 2020-09-19 19:02 2 | 3 | from django.conf import settings 4 | import django.contrib.auth.models 5 | import django.contrib.auth.validators 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | import django.utils.timezone 9 | 10 | 11 | class Migration(migrations.Migration): 12 | 13 | initial = True 14 | 15 | dependencies = [ 16 | ('auth', '0012_alter_user_first_name_max_length'), 17 | ] 18 | 19 | operations = [ 20 | migrations.CreateModel( 21 | name='User', 22 | fields=[ 23 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 24 | ('password', models.CharField(max_length=128, verbose_name='password')), 25 | ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), 26 | ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), 27 | ('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')), 28 | ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), 29 | ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), 30 | ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), 31 | ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), 32 | ('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')), 33 | ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), 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 | migrations.CreateModel( 47 | name='Category', 48 | fields=[ 49 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 50 | ('name', models.CharField(max_length=20)), 51 | ], 52 | options={ 53 | 'verbose_name_plural': 'categories', 54 | }, 55 | ), 56 | migrations.CreateModel( 57 | name='AuctionListening', 58 | fields=[ 59 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 60 | ('title', models.CharField(max_length=50)), 61 | ('description', models.TextField(max_length=1000)), 62 | ('starting_bid', models.DecimalField(decimal_places=2, max_digits=6)), 63 | ('image_url', models.URLField(blank=True)), 64 | ('category', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='auctions.category')), 65 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 66 | ], 67 | ), 68 | ] 69 | -------------------------------------------------------------------------------- /project2/auctions/migrations/0002_auto_20200920_1520.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.1 on 2020-09-20 13:20 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('auctions', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='auctionlistening', 17 | name='active', 18 | field=models.BooleanField(default=True), 19 | ), 20 | migrations.AddField( 21 | model_name='auctionlistening', 22 | name='current_price', 23 | field=models.DecimalField(decimal_places=2, default=0, max_digits=8), 24 | preserve_default=False, 25 | ), 26 | migrations.AlterField( 27 | model_name='auctionlistening', 28 | name='starting_bid', 29 | field=models.DecimalField(decimal_places=2, max_digits=8), 30 | ), 31 | migrations.CreateModel( 32 | name='Bid', 33 | fields=[ 34 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 35 | ('auction', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auctions.auctionlistening')), 36 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), 37 | ], 38 | ), 39 | ] 40 | -------------------------------------------------------------------------------- /project2/auctions/migrations/0003_favorites.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.1 on 2020-09-23 08:46 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('auctions', '0002_auto_20200920_1520'), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='Favorites', 17 | fields=[ 18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('auction', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auctions.auctionlistening')), 20 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 21 | ], 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /project2/auctions/migrations/0004_auto_20200923_1101.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.1 on 2020-09-23 09:01 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('auctions', '0003_favorites'), 11 | ] 12 | 13 | operations = [ 14 | migrations.RemoveField( 15 | model_name='favorites', 16 | name='user', 17 | ), 18 | migrations.AddField( 19 | model_name='favorites', 20 | name='users', 21 | field=models.ManyToManyField(to=settings.AUTH_USER_MODEL), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /project2/auctions/migrations/0005_bid_amount.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.1 on 2020-09-23 12:37 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('auctions', '0004_auto_20200923_1101'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='bid', 15 | name='amount', 16 | field=models.DecimalField(decimal_places=2, default=0, max_digits=8), 17 | preserve_default=False, 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /project2/auctions/migrations/0006_auto_20200924_2155.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.1 on 2020-09-24 19:55 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('auctions', '0005_bid_amount'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='auctionlistening', 16 | name='favoured', 17 | field=models.ManyToManyField(related_name='user_favoured', to=settings.AUTH_USER_MODEL), 18 | ), 19 | migrations.DeleteModel( 20 | name='Favorites', 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /project2/auctions/migrations/0007_auto_20200926_0002.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.1 on 2020-09-25 22:02 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('auctions', '0006_auto_20200924_2155'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='auctionlistening', 16 | name='favoured', 17 | field=models.ManyToManyField(blank=True, related_name='user_favoured', to=settings.AUTH_USER_MODEL), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /project2/auctions/migrations/0008_comment.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.1 on 2020-09-26 09:16 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('auctions', '0007_auto_20200926_0002'), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='Comment', 17 | fields=[ 18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('created_on', models.DateTimeField(auto_now_add=True)), 20 | ('auction', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='auctions.auctionlistening')), 21 | ('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)), 22 | ], 23 | options={ 24 | 'ordering': ['created_on'], 25 | }, 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /project2/auctions/migrations/0009_comment_body.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.1 on 2020-09-26 09:19 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('auctions', '0008_comment'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='comment', 15 | name='body', 16 | field=models.TextField(default=''), 17 | preserve_default=False, 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /project2/auctions/migrations/0010_bookmark.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.1 on 2020-09-26 12:25 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('auctions', '0009_comment_body'), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='Bookmark', 17 | fields=[ 18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('auction', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='bookmarks', to='auctions.auctionlistening')), 20 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='bookmarks', to=settings.AUTH_USER_MODEL)), 21 | ], 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /project2/auctions/migrations/0011_auto_20200926_1433.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.1 on 2020-09-26 12:33 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('auctions', '0010_bookmark'), 11 | ] 12 | 13 | operations = [ 14 | migrations.RemoveField( 15 | model_name='bookmark', 16 | name='user', 17 | ), 18 | migrations.AddField( 19 | model_name='bookmark', 20 | name='user', 21 | field=models.ManyToManyField(blank=True, related_name='bookmarks', to=settings.AUTH_USER_MODEL), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /project2/auctions/migrations/0012_delete_bookmark.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.1 on 2020-09-26 13:03 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('auctions', '0011_auto_20200926_1433'), 10 | ] 11 | 12 | operations = [ 13 | migrations.DeleteModel( 14 | name='Bookmark', 15 | ), 16 | ] 17 | -------------------------------------------------------------------------------- /project2/auctions/migrations/0013_category_slug.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.1 on 2020-09-26 16:28 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('auctions', '0012_delete_bookmark'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='category', 15 | name='slug', 16 | field=models.SlugField(null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /project2/auctions/migrations/0014_auto_20200926_1835.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.1 on 2020-09-26 16:35 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('auctions', '0013_category_slug'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='category', 15 | name='slug', 16 | field=models.SlugField(unique=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /project2/auctions/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbsth/cs50w/90b58e0342ac3813fe0ee97b3191bd22836901fe/project2/auctions/migrations/__init__.py -------------------------------------------------------------------------------- /project2/auctions/models.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import AbstractUser 2 | from django.db import models 3 | from django.conf import settings 4 | from django.urls import reverse 5 | from django.utils.text import slugify 6 | 7 | class User(AbstractUser): 8 | pass 9 | 10 | 11 | class Category(models.Model): 12 | name = models.CharField(max_length=20) 13 | slug = models.SlugField(null=False, unique=True) 14 | 15 | def save(self, *args, **kwargs): 16 | self.slug = slugify(self.name) 17 | return super(Category, self).save(*args, **kwargs) 18 | 19 | def __str__(self): 20 | return self.name 21 | 22 | class Meta: 23 | verbose_name_plural = "categories" 24 | 25 | def get_absolute_url(self): 26 | return reverse('category_listings', kwargs={'slug': self.slug}) 27 | 28 | 29 | class AuctionListening(models.Model): 30 | title = models.CharField(max_length=50) 31 | description = models.TextField(max_length=1000) 32 | starting_bid = models.DecimalField(decimal_places=2, max_digits=8) 33 | image_url = models.URLField(blank=True) 34 | category = models.ForeignKey(Category, default=None, null=True, on_delete=models.SET_NULL) 35 | user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) 36 | current_price = models.DecimalField(decimal_places=2, max_digits=8) 37 | active = models.BooleanField(default=True) 38 | favoured = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name="user_favoured", blank=True) 39 | 40 | def save(self, *args, **kwargs): 41 | if not self.pk: 42 | self.current_price = self.starting_bid 43 | super().save(*args, **kwargs) 44 | 45 | def __str__(self): 46 | return self.title 47 | 48 | def get_absolute_url(self): 49 | return reverse('auction_view', kwargs={"pk": self.pk}) 50 | 51 | 52 | 53 | class Bid(models.Model): 54 | auction = models.ForeignKey(AuctionListening, on_delete=models.CASCADE) 55 | user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT) 56 | amount = models.DecimalField(decimal_places=2, max_digits=8) 57 | 58 | def __str__(self): 59 | return f"{self.amount} bid on {self.auction} by {self.user}" 60 | 61 | 62 | class Comment(models.Model): 63 | auction = models.ForeignKey(AuctionListening, on_delete=models.CASCADE, related_name='comments') 64 | user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True) 65 | body = models.TextField() 66 | created_on = models.DateTimeField(auto_now_add=True) 67 | 68 | class Meta: 69 | ordering = ['created_on'] 70 | 71 | def __str__(self): 72 | return f"{self.user} comment on {self.auction}" 73 | -------------------------------------------------------------------------------- /project2/auctions/static/auctions/css/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | display: flex; 3 | min-height: 100vh; 4 | flex-direction: column; 5 | } 6 | 7 | main { 8 | flex: 1 0 auto; 9 | } 10 | 11 | .comment-box{ 12 | margin-bottom: 1em; 13 | } 14 | 15 | .comment-author{ 16 | font-weight: bold; 17 | } 18 | 19 | .comment-date{ 20 | font-weight: lighter; 21 | } -------------------------------------------------------------------------------- /project2/auctions/static/auctions/js/init.js: -------------------------------------------------------------------------------- 1 | document.addEventListener('DOMContentLoaded', function() { 2 | var elems = document.querySelectorAll('select'); 3 | var instances = M.FormSelect.init(elems, {}); 4 | }); 5 | 6 | document.addEventListener('DOMContentLoaded', function() { 7 | var elems = document.querySelectorAll('.materialboxed'); 8 | var instances = M.Materialbox.init(elems, {}); 9 | }); -------------------------------------------------------------------------------- /project2/auctions/templates/auctions/auction_view.html: -------------------------------------------------------------------------------- 1 | {% extends "auctions/layout.html" %} 2 | {% load materializecss %} 3 | 4 | {% block body %} 5 |
6 |
7 |
8 |
9 | {{ auction.title }} 10 |
11 |
12 |
13 |
14 |
15 |
16 | 17 |
18 |
19 | {% if auction.active %} 20 |
21 |
22 | Starting bid: ${{ auction.starting_bid }} 23 |
24 |
25 | Current top bid: ${{ auction.current_price }} 26 |
27 |
28 |
29 |
30 |
31 | {% if user.is_authenticated %} 32 |
33 | {% csrf_token %} 34 | {{ bid_form }} 35 |
36 |
37 | 38 |
39 | {% else %} 40 |
41 | Please log in to bid on auctions 42 |
43 | {% endif %} 44 |
45 |
46 |
47 | {% if user.is_authenticated %} 48 |
49 | {% if user == auction.user %} 50 | End auction 51 | {% endif %} 52 | {% if favoured %} 53 | Remove from watchlist 54 | {% else %} 55 | Add to watchlist 56 | {% endif %} 57 |
58 | {% endif %} 59 | {% else %} 60 |
That auction was won by {{ top_bid.user }} with ${{ top_bid.amount }} bid.
61 | {% endif %} 62 |
63 |
64 | {{ auction.description }} 65 |
66 |
67 |
68 |

Comments:

69 | {% for comment in comments %} 70 |
71 | {{ comment.user }} {{ comment.created_on }} 72 |

{{ comment.body }}

73 |
74 | {% empty %} 75 | No comments yet! 76 | {% endfor %} 77 | 78 | {% if user.is_authenticated %} 79 |
Post your comment:
80 |
81 |
82 | {% csrf_token %} 83 |
84 | {{ comment_form|materializecss }} 85 |
86 |
87 | 88 |
89 |
90 |
91 | {% endif %} 92 | 93 |
94 |
95 |
96 | {% endblock body %} -------------------------------------------------------------------------------- /project2/auctions/templates/auctions/bookmarks.html: -------------------------------------------------------------------------------- 1 | {% extends "auctions/layout.html" %} 2 | 3 | {% block title %} Bookmarks {% endblock title %} 4 | 5 | {% block body %} 6 |
7 |
8 |

Bookmarks

9 | {% for auction in bookmarks %} 10 |
11 |
12 | 13 |

{{ auction.description }}

14 |
15 |
16 | {% endfor %} 17 |
18 |
19 | {% endblock body %} -------------------------------------------------------------------------------- /project2/auctions/templates/auctions/categories.html: -------------------------------------------------------------------------------- 1 | {% extends "auctions/layout.html" %} 2 | 3 | {% block title %}Categories{% endblock title %} 4 | 5 | {% block body %} 6 |
7 |
8 |

Categories:

9 | {% for category in object_list %} 10 |
11 | 14 |
15 | {% endfor %} 16 |
17 |
18 | {% endblock body %} -------------------------------------------------------------------------------- /project2/auctions/templates/auctions/category_listings.html: -------------------------------------------------------------------------------- 1 | {% extends "auctions/layout.html" %} 2 | 3 | {% block body %} 4 |

{{ category }} Listings

5 |
6 | {% for auction in object_list %} 7 |
8 |
9 |
10 | {{ auction.title }} 11 |
12 |
13 | 14 |
15 |
16 |

{{ auction.description }}

17 |
18 |
19 |
20 |
21 | Current price: ${{ auction.current_price }} 22 | Bid 23 |
24 |
25 |
26 | {% empty %} 27 | No listings for that category 28 | {% endfor %} 29 |
30 | 31 | {% endblock %} -------------------------------------------------------------------------------- /project2/auctions/templates/auctions/includes/footer.html: -------------------------------------------------------------------------------- 1 |
2 | 7 |
-------------------------------------------------------------------------------- /project2/auctions/templates/auctions/includes/header.html: -------------------------------------------------------------------------------- 1 | 18 | {#

Auctions

#} 19 | {#
#} 20 | {# {% if user.is_authenticated %}#} 21 | {# Signed in as {{ user.username }}.#} 22 | {# {% else %}#} 23 | {# Not signed in.#} 24 | {# {% endif %}#} 25 | {#
#} 26 | {##} 46 | {#
#} -------------------------------------------------------------------------------- /project2/auctions/templates/auctions/index.html: -------------------------------------------------------------------------------- 1 | {% extends "auctions/layout.html" %} 2 | 3 | {% block body %} 4 |

Active Listings

5 |
6 | {% for auction in object_list %} 7 | {% if auction.active %} 8 |
9 |
10 |
11 | {{ auction.title }} 12 |
13 |
14 | 15 |
16 |
17 |

{{ auction.description }}

18 |
19 |
20 |
21 |
22 | Current price: ${{ auction.current_price }} 23 | Bid 24 |
25 |
26 |
27 | {% endif %} 28 | {% empty %} 29 | empty, ops 30 | {% endfor %} 31 |
32 | 33 | {% endblock %} -------------------------------------------------------------------------------- /project2/auctions/templates/auctions/layout.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | 6 | {% block title %}Auctions{% endblock %} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | {% include "auctions/includes/header.html" %} 23 |
24 | 25 |
26 |
27 | {% block body %} 28 | {% endblock %} 29 |
30 |
31 | 32 | {% include "auctions/includes/footer.html" %} 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /project2/auctions/templates/auctions/login.html: -------------------------------------------------------------------------------- 1 | {% extends "auctions/layout.html" %} 2 | 3 | {% block body %} 4 | 5 |

Login

6 | 7 | {% if message %} 8 |
{{ message }}
9 | {% endif %} 10 | 11 |
12 | {% csrf_token %} 13 |
14 | 15 |
16 |
17 | 18 |
19 | 20 |
21 | 22 | Don't have an account? Register here. 23 | 24 | {% endblock %} -------------------------------------------------------------------------------- /project2/auctions/templates/auctions/new_auction.html: -------------------------------------------------------------------------------- 1 | {% extends "auctions/layout.html" %} 2 | {% load materializecss %} 3 | 4 | {% block title %} 5 | New auction 6 | {% endblock title %} 7 | 8 | {% block body %} 9 |
10 | {% csrf_token %} 11 | {{ form|materializecss }} 12 | 13 |
14 | {% endblock body %} -------------------------------------------------------------------------------- /project2/auctions/templates/auctions/register.html: -------------------------------------------------------------------------------- 1 | {% extends "auctions/layout.html" %} 2 | 3 | {% block body %} 4 | 5 |

Register

6 | 7 | {% if message %} 8 |
{{ message }}
9 | {% endif %} 10 | 11 |
12 | {% csrf_token %} 13 |
14 | 15 |
16 |
17 | 18 |
19 |
20 | 21 |
22 |
23 | 24 |
25 | 26 |
27 | 28 | Already have an account? Log In here. 29 | 30 | {% endblock %} -------------------------------------------------------------------------------- /project2/auctions/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /project2/auctions/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from . import views 4 | 5 | urlpatterns = [ 6 | path("", views.IndexListView.as_view(), name="index"), 7 | path("login", views.login_view, name="login"), 8 | path("logout", views.logout_view, name="logout"), 9 | path("register", views.register, name="register"), 10 | path("new_auction", views.new_auction, name="new_auction"), 11 | path("auction//", views.auction_view, name='auction_view'), 12 | path("auction//favourite", views.favourite_post, name='favourite_post'), 13 | path("auction//end", views.end_auction, name="end_auction"), 14 | path("bookmarks", views.bookmarks, name="bookmarks"), 15 | path("categories", views.CategoriesView.as_view(), name="categories"), 16 | path("categories/", views.CategoryListings.as_view(), name="category_listings") 17 | ] 18 | -------------------------------------------------------------------------------- /project2/auctions/views.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import authenticate, login, logout 2 | from django.db import IntegrityError 3 | from django.http import HttpResponse, HttpResponseRedirect 4 | from django.shortcuts import render, get_object_or_404, redirect 5 | from django.urls import reverse 6 | from django.contrib.auth.decorators import login_required 7 | from django.views.generic.list import ListView 8 | from django.views.generic.detail import DetailView 9 | from django.views.generic.base import TemplateView 10 | from django.db.models import Max 11 | from django.views import View 12 | from django.contrib import messages 13 | 14 | from .models import User, AuctionListening, Bid, Category 15 | from .forms import NewAuctionForm, BidForm, CommentForm 16 | 17 | from decimal import Decimal 18 | 19 | 20 | class IndexListView(ListView): 21 | model = AuctionListening 22 | template_name = "auctions/index.html" 23 | paginate_by = 10 24 | 25 | def get_context_data(self, **kwargs): 26 | context = super().get_context_data(**kwargs) 27 | return context 28 | 29 | 30 | def login_view(request): 31 | if request.method == "POST": 32 | 33 | # Attempt to sign user in 34 | username = request.POST["username"] 35 | password = request.POST["password"] 36 | user = authenticate(request, username=username, password=password) 37 | 38 | # Check if authentication successful 39 | if user is not None: 40 | login(request, user) 41 | return HttpResponseRedirect(reverse("index")) 42 | else: 43 | return render(request, "auctions/login.html", { 44 | "message": "Invalid username and/or password." 45 | }) 46 | else: 47 | return render(request, "auctions/login.html") 48 | 49 | 50 | def logout_view(request): 51 | logout(request) 52 | return HttpResponseRedirect(reverse("index")) 53 | 54 | 55 | def register(request): 56 | if request.method == "POST": 57 | username = request.POST["username"] 58 | email = request.POST["email"] 59 | 60 | # Ensure password matches confirmation 61 | password = request.POST["password"] 62 | confirmation = request.POST["confirmation"] 63 | if password != confirmation: 64 | return render(request, "auctions/register.html", { 65 | "message": "Passwords must match." 66 | }) 67 | 68 | # Attempt to create new user 69 | try: 70 | user = User.objects.create_user(username, email, password) 71 | user.save() 72 | except IntegrityError: 73 | return render(request, "auctions/register.html", { 74 | "message": "Username already taken." 75 | }) 76 | login(request, user) 77 | return HttpResponseRedirect(reverse("index")) 78 | else: 79 | return render(request, "auctions/register.html") 80 | 81 | 82 | @login_required 83 | def new_auction(request): 84 | if request.method == "POST": 85 | form = NewAuctionForm(request.POST) 86 | if form.is_valid(): 87 | obj = form.save(commit=False) 88 | obj.user = request.user 89 | obj.save() 90 | return HttpResponseRedirect(reverse("index")) 91 | else: 92 | form = NewAuctionForm() 93 | 94 | return render(request, "auctions/new_auction.html", { 95 | "form": form 96 | }) 97 | 98 | 99 | def auction_view(request, pk): 100 | auction = get_object_or_404(AuctionListening, pk=pk) 101 | favoured = request.user in auction.favoured.all() 102 | 103 | print(request.method) 104 | 105 | try: 106 | top_bid = auction.bid_set.all().order_by("-amount")[0] 107 | except: 108 | top_bid = None 109 | 110 | if request.method == "POST": 111 | # new bid was submitted 112 | if 'bid' in request.POST: 113 | bid_form = BidForm(request.POST, auction=auction) 114 | if bid_form.is_valid() and request.user.is_authenticated: 115 | temp = bid_form.save(commit=False) 116 | temp.user = request.user 117 | temp.auction = auction 118 | temp.save() 119 | auction.current_price = temp.amount 120 | auction.save() 121 | return redirect(auction_view, pk=auction.pk) 122 | 123 | # new comment was submitted 124 | elif 'comment' in request.POST: 125 | comment_form = CommentForm(request.POST) 126 | if comment_form.is_valid() and request.user.is_authenticated: 127 | temp = comment_form.save(commit=False) 128 | temp.user = request.user 129 | temp.auction = auction 130 | temp.save() 131 | return redirect(auction_view, pk=auction.pk) 132 | 133 | else: 134 | minimum_bid = auction.current_price + Decimal(0.01).quantize(Decimal('1.00')) 135 | bid_form = BidForm(initial={"amount": minimum_bid}, auction=auction) 136 | comment_form = CommentForm() 137 | 138 | return render(request, "auctions/auction_view.html", { 139 | "auction": auction, 140 | "bid_form": bid_form, 141 | "favoured": favoured, 142 | "top_bid": top_bid, 143 | "comment_form": comment_form, 144 | "comments": auction.comments.all() 145 | }) 146 | 147 | 148 | def favourite_post(request, pk): 149 | # check if that auction is favoured by user or not 150 | if request.user.is_authenticated: 151 | auction = AuctionListening.objects.get(pk=pk) 152 | if request.user in auction.favoured.all(): 153 | # User already favoured, unfavourite auction 154 | auction.favoured.remove(request.user) 155 | else: 156 | # add auction to favourites 157 | auction.favoured.add(request.user) 158 | return HttpResponseRedirect(reverse("auction_view", args=[pk])) 159 | 160 | 161 | def end_auction(request, pk): 162 | auction = AuctionListening.objects.get(pk=pk) 163 | if request.user.is_authenticated and auction.user == request.user: 164 | # user is authenticated, and owner of that auction 165 | auction.active = False 166 | auction.save() 167 | 168 | return HttpResponseRedirect(reverse("auction_view", args=[pk])) 169 | 170 | 171 | @login_required 172 | def bookmarks(request): 173 | bkmrk = request.user.user_favoured.all() 174 | return render(request, "auctions/bookmarks.html", 175 | {"bookmarks": bkmrk} 176 | ) 177 | 178 | 179 | class CategoriesView(ListView): 180 | template_name = "auctions/categories.html" 181 | model = Category 182 | 183 | def get_context_data(self, **kwargs): 184 | context = super().get_context_data(**kwargs) 185 | return context 186 | 187 | 188 | class CategoryListings(ListView): 189 | template_name = "auctions/category_listings.html" 190 | model = AuctionListening 191 | 192 | def get_context_data(self, **kwargs): 193 | context = super().get_context_data(**kwargs) 194 | context["category"] = self.kwargs["slug"].title() 195 | return context 196 | 197 | def get_queryset(self): 198 | return AuctionListening.objects.filter(category__slug=self.kwargs["slug"], active=True) -------------------------------------------------------------------------------- /project2/commerce/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbsth/cs50w/90b58e0342ac3813fe0ee97b3191bd22836901fe/project2/commerce/__init__.py -------------------------------------------------------------------------------- /project2/commerce/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for commerce 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.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', 'commerce.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /project2/commerce/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for commerce project. 3 | 4 | Generated by 'django-admin startproject' using Django 3.0.2. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.0/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/3.0/ref/settings/ 11 | """ 12 | 13 | import os 14 | # from django.urls import reverse 15 | 16 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 17 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 18 | 19 | 20 | # Quick-start development settings - unsuitable for production 21 | # See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ 22 | 23 | # SECURITY WARNING: keep the secret key used in production secret! 24 | SECRET_KEY = '6ps8j!crjgrxt34cqbqn7x&b3y%(fny8k8nh21+qa)%ws3fh!q' 25 | 26 | # SECURITY WARNING: don't run with debug turned on in production! 27 | DEBUG = True 28 | 29 | ALLOWED_HOSTS = [] 30 | 31 | 32 | # Application definition 33 | 34 | INSTALLED_APPS = [ 35 | 'auctions', 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 | 'materializecssform' 43 | ] 44 | 45 | MIDDLEWARE = [ 46 | 'django.middleware.security.SecurityMiddleware', 47 | 'django.contrib.sessions.middleware.SessionMiddleware', 48 | 'django.middleware.common.CommonMiddleware', 49 | 'django.middleware.csrf.CsrfViewMiddleware', 50 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 51 | 'django.contrib.messages.middleware.MessageMiddleware', 52 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 53 | ] 54 | 55 | ROOT_URLCONF = 'commerce.urls' 56 | 57 | TEMPLATES = [ 58 | { 59 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 60 | 'DIRS': [], 61 | 'APP_DIRS': True, 62 | 'OPTIONS': { 63 | 'context_processors': [ 64 | 'django.template.context_processors.debug', 65 | 'django.template.context_processors.request', 66 | 'django.contrib.auth.context_processors.auth', 67 | 'django.contrib.messages.context_processors.messages', 68 | ], 69 | }, 70 | }, 71 | ] 72 | 73 | WSGI_APPLICATION = 'commerce.wsgi.application' 74 | 75 | 76 | # Database 77 | # https://docs.djangoproject.com/en/3.0/ref/settings/#databases 78 | 79 | DATABASES = { 80 | 'default': { 81 | 'ENGINE': 'django.db.backends.sqlite3', 82 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 83 | } 84 | } 85 | 86 | AUTH_USER_MODEL = 'auctions.User' 87 | 88 | # Password validation 89 | # https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators 90 | 91 | AUTH_PASSWORD_VALIDATORS = [ 92 | { 93 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 94 | }, 95 | { 96 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 97 | }, 98 | { 99 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 100 | }, 101 | { 102 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 103 | }, 104 | ] 105 | 106 | 107 | # Internationalization 108 | # https://docs.djangoproject.com/en/3.0/topics/i18n/ 109 | 110 | LANGUAGE_CODE = 'en-us' 111 | 112 | TIME_ZONE = 'UTC' 113 | 114 | USE_I18N = True 115 | 116 | USE_L10N = True 117 | 118 | USE_TZ = True 119 | 120 | 121 | # Static files (CSS, JavaScript, Images) 122 | # https://docs.djangoproject.com/en/3.0/howto/static-files/ 123 | 124 | STATIC_URL = '/static/' 125 | 126 | LOGIN_URL = 'login' 127 | -------------------------------------------------------------------------------- /project2/commerce/urls.py: -------------------------------------------------------------------------------- 1 | """commerce URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/3.0/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.contrib import admin 17 | from django.urls import include, path 18 | 19 | urlpatterns = [ 20 | path("admin/", admin.site.urls), 21 | path("", include("auctions.urls")) 22 | ] -------------------------------------------------------------------------------- /project2/commerce/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for commerce 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.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', 'commerce.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /project2/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 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'commerce.settings') 9 | try: 10 | from django.core.management import execute_from_command_line 11 | except ImportError as exc: 12 | raise ImportError( 13 | "Couldn't import Django. Are you sure it's installed and " 14 | "available on your PYTHONPATH environment variable? Did you " 15 | "forget to activate a virtual environment?" 16 | ) from exc 17 | execute_from_command_line(sys.argv) 18 | 19 | 20 | if __name__ == '__main__': 21 | main() 22 | -------------------------------------------------------------------------------- /project3/mail/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbsth/cs50w/90b58e0342ac3813fe0ee97b3191bd22836901fe/project3/mail/__init__.py -------------------------------------------------------------------------------- /project3/mail/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /project3/mail/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class MailConfig(AppConfig): 5 | name = 'mail' 6 | -------------------------------------------------------------------------------- /project3/mail/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2 on 2021-07-13 19:51 2 | 3 | from django.conf import settings 4 | import django.contrib.auth.models 5 | import django.contrib.auth.validators 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | import django.utils.timezone 9 | 10 | 11 | class Migration(migrations.Migration): 12 | 13 | initial = True 14 | 15 | dependencies = [ 16 | ('auth', '0012_alter_user_first_name_max_length'), 17 | ] 18 | 19 | operations = [ 20 | migrations.CreateModel( 21 | name='User', 22 | fields=[ 23 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 24 | ('password', models.CharField(max_length=128, verbose_name='password')), 25 | ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), 26 | ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), 27 | ('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')), 28 | ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), 29 | ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), 30 | ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), 31 | ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), 32 | ('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')), 33 | ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), 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 | migrations.CreateModel( 47 | name='Email', 48 | fields=[ 49 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 50 | ('subject', models.CharField(max_length=255)), 51 | ('body', models.TextField(blank=True)), 52 | ('timestamp', models.DateTimeField(auto_now_add=True)), 53 | ('read', models.BooleanField(default=False)), 54 | ('archived', models.BooleanField(default=False)), 55 | ('recipients', models.ManyToManyField(related_name='emails_received', to=settings.AUTH_USER_MODEL)), 56 | ('sender', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='emails_sent', to=settings.AUTH_USER_MODEL)), 57 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='emails', to=settings.AUTH_USER_MODEL)), 58 | ], 59 | ), 60 | ] 61 | -------------------------------------------------------------------------------- /project3/mail/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbsth/cs50w/90b58e0342ac3813fe0ee97b3191bd22836901fe/project3/mail/migrations/__init__.py -------------------------------------------------------------------------------- /project3/mail/models.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import AbstractUser 2 | from django.db import models 3 | 4 | 5 | class User(AbstractUser): 6 | pass 7 | 8 | 9 | class Email(models.Model): 10 | user = models.ForeignKey("User", on_delete=models.CASCADE, related_name="emails") 11 | sender = models.ForeignKey("User", on_delete=models.PROTECT, related_name="emails_sent") 12 | recipients = models.ManyToManyField("User", related_name="emails_received") 13 | subject = models.CharField(max_length=255) 14 | body = models.TextField(blank=True) 15 | timestamp = models.DateTimeField(auto_now_add=True) 16 | read = models.BooleanField(default=False) 17 | archived = models.BooleanField(default=False) 18 | 19 | def serialize(self): 20 | return { 21 | "id": self.id, 22 | "sender": self.sender.email, 23 | "recipients": [user.email for user in self.recipients.all()], 24 | "subject": self.subject, 25 | "body": self.body, 26 | "timestamp": self.timestamp.strftime("%b %d %Y, %I:%M %p"), 27 | "read": self.read, 28 | "archived": self.archived 29 | } 30 | -------------------------------------------------------------------------------- /project3/mail/static/mail/inbox.js: -------------------------------------------------------------------------------- 1 | let current_view; 2 | 3 | document.addEventListener('DOMContentLoaded', function() { 4 | 5 | // Use buttons to toggle between views 6 | document.querySelector('#inbox').addEventListener('click', () => load_mailbox('inbox')); 7 | document.querySelector('#sent').addEventListener('click', () => load_mailbox('sent')); 8 | document.querySelector('#archived').addEventListener('click', () => load_mailbox('archive')); 9 | document.querySelector('#compose').addEventListener('click', compose_email); 10 | 11 | // By default, load the inbox 12 | load_mailbox('inbox'); 13 | }); 14 | 15 | function compose_email() { 16 | 17 | // Show compose view and hide other views 18 | document.querySelector('#emails-view').style.display = 'none'; 19 | document.querySelector('#single-email-view').style.display = 'none'; 20 | document.querySelector('#compose-view').style.display = 'block'; 21 | 22 | // Clear out composition fields 23 | document.querySelector('#compose-recipients').value = ''; 24 | document.querySelector('#compose-subject').value = ''; 25 | document.querySelector('#compose-body').value = ''; 26 | 27 | // Turn off default button send behaviour 28 | document.querySelector('#compose-form').addEventListener("submit", function(evt){ 29 | evt.preventDefault(); 30 | //window.history.back(); 31 | }, true); 32 | 33 | // Add event listener to send email button 34 | document.querySelector('input[type="submit"]').addEventListener('click', send_email); 35 | } 36 | 37 | function load_mailbox(mailbox) { 38 | 39 | // Store current view in variable 40 | current_view = mailbox; 41 | 42 | // Show the mailbox and hide other views 43 | document.querySelector('#emails-view').style.display = 'block'; 44 | document.querySelector('#single-email-view').style.display = 'none'; 45 | document.querySelector('#compose-view').style.display = 'none'; 46 | 47 | // Show the mailbox name 48 | document.querySelector('#emails-view').innerHTML = `

${mailbox.charAt(0).toUpperCase() + mailbox.slice(1)}

`; 49 | 50 | // Add the email list container 51 | if(document.getElementById("emails-table") == null){ 52 | const element = document.createElement('div'); 53 | element.id = "emails-table"; 54 | 55 | document.querySelector('div[id="emails-view"]').append(element); 56 | } 57 | 58 | let tableBody = document.querySelector('div[id="emails-table"]'); 59 | 60 | // Clear old emails 61 | tableBody.innerHTML = ''; 62 | 63 | // Fetch the emails 64 | fetch(`emails/${mailbox}`) 65 | .then(response => response.json()) 66 | .then(emails => processEmails(emails)); 67 | 68 | let processEmails = (emails) => { 69 | emails.forEach(function (email){ 70 | console.log(email); 71 | const temp_element = document.createElement('a'); 72 | temp_element.classList.add('email-link'); 73 | temp_element.innerHTML= ` 74 |
75 | 76 | 77 | 78 |
`; 79 | tableBody.append(temp_element); 80 | temp_element.addEventListener('click', ()=>display_email(email.id)); 81 | }) 82 | } 83 | } 84 | 85 | function send_email(){ 86 | // get email data 87 | let recipients = document.querySelector('#compose-recipients').value; 88 | let subject = document.querySelector('#compose-subject').value; 89 | let body = document.querySelector("#compose-body").value; 90 | 91 | fetch('/emails', { 92 | method: 'POST', 93 | body: JSON.stringify({ 94 | recipients: `${recipients}`, 95 | subject: `${subject}`, 96 | body: `${body}` 97 | }) 98 | }) 99 | .then(response => response.json()) 100 | .then(()=>load_mailbox('sent')); 101 | } 102 | 103 | function display_email(number){ 104 | 105 | document.querySelector('#emails-table').style.display = 'none'; 106 | document.querySelector('#compose-view').style.display = 'none'; 107 | document.querySelector('#single-email-view').style.display = 'block'; 108 | document.querySelector('#emails-view').innerHTML = ``; 109 | 110 | fetch(`/emails/${number}`) 111 | .then(response => response.json()) 112 | .then(email => processEmail(email)); 113 | 114 | let processEmail = (email) =>{ 115 | document.querySelector("#single-email-from").innerHTML=`${email.sender}`; 116 | document.querySelector("#single-email-to").innerHTML=`${email.recipients}`; 117 | document.querySelector("#single-email-subject").innerHTML=`${email.subject}`; 118 | document.querySelector("#single-email-timestamp").innerHTML=`${email.timestamp}`; 119 | document.querySelector("#single-email-content").innerHTML=`${email.body}`; 120 | 121 | // mark email as read 122 | fetch(`/emails/${email.id}`, { 123 | method: 'PUT', 124 | body: JSON.stringify({ 125 | read: true}) 126 | }) 127 | 128 | // handle archive/unarchive behaviour 129 | let old_archiveButton = document.querySelector("#archive-button"); 130 | let archiveButton = old_archiveButton.cloneNode(true); 131 | old_archiveButton.parentNode.replaceChild(archiveButton, old_archiveButton); 132 | 133 | let old_unarchiveButton = document.querySelector("#unarchive-button"); 134 | let unarchiveButton = old_unarchiveButton.cloneNode(true); 135 | old_unarchiveButton.parentNode.replaceChild(unarchiveButton, old_unarchiveButton); 136 | 137 | let old_replyButton = document.querySelector("#reply-button"); 138 | let replyButton = old_replyButton.cloneNode(true); 139 | old_replyButton.parentNode.replaceChild(replyButton, old_replyButton); 140 | 141 | if(current_view == 'sent'){ 142 | archiveButton.style.display='none'; 143 | } 144 | else{ 145 | switch (email.archived){ 146 | case false: 147 | // email is not archived 148 | archiveButton.style.display='inline'; 149 | unarchiveButton.style.display='none'; 150 | archiveButton.addEventListener('click', ()=>archive_email(email.id)); 151 | break; 152 | case true: 153 | // email is archived 154 | archiveButton.style.display='none'; 155 | unarchiveButton.style.display='inline'; 156 | unarchiveButton.addEventListener('click', ()=>unarchive_email(email.id)); 157 | break; 158 | } 159 | } 160 | 161 | replyButton.addEventListener('click', ()=>reply_email(email)); 162 | 163 | 164 | 165 | } 166 | } 167 | 168 | function archive_email(number){ 169 | // mark email as archived 170 | fetch(`/emails/${number}`, { 171 | method: 'PUT', 172 | body: JSON.stringify({ 173 | archived: true}) 174 | }).then( 175 | ()=>{ 176 | console.log("ARCHIVED"); 177 | load_mailbox('archive') 178 | } 179 | ) 180 | } 181 | 182 | function unarchive_email(number){ 183 | // mark email as unarchived 184 | fetch(`/emails/${number}`, { 185 | method: 'PUT', 186 | body: JSON.stringify({ 187 | archived: false}) 188 | }).then( 189 | ()=>{ 190 | console.log("UNARCHIVED"); 191 | load_mailbox('inbox') 192 | } 193 | ) 194 | } 195 | 196 | function reply_email(email){ 197 | compose_email(); 198 | document.querySelector('#compose-recipients').value = email.sender; 199 | document.querySelector('#compose-subject').value = `Re: ${email.subject}`; 200 | document.querySelector('#compose-body').value = `On ${email.timestamp} ${email.sender} wrote:\n${email.body}`; 201 | } 202 | 203 | -------------------------------------------------------------------------------- /project3/mail/static/mail/styles.css: -------------------------------------------------------------------------------- 1 | textarea { 2 | min-height: 400px; 3 | } 4 | 5 | .read{ 6 | background-color: darkgray; 7 | cursor: pointer; 8 | } 9 | 10 | .unread{ 11 | background-color: whitesmoke; 12 | cursor: pointer; 13 | } 14 | 15 | .email-sender{ 16 | font-weight: bold; 17 | } 18 | 19 | .email-date{ 20 | text-align: right; 21 | } 22 | 23 | .email-link, .email-link:hover{ 24 | color: black; 25 | } 26 | 27 | .email-header{ 28 | font-weight: bold; 29 | } -------------------------------------------------------------------------------- /project3/mail/templates/mail/inbox.html: -------------------------------------------------------------------------------- 1 | {% extends "mail/layout.html" %} 2 | {% load static %} 3 | 4 | {% block body %} 5 |

{{ request.user.email }}

6 | 7 | 8 | 9 | 10 | 11 | Log Out 12 |
13 | 14 |
15 |
16 | 17 |
18 |

New Email

19 |
20 |
21 | From: 22 |
23 |
24 | To: 25 |
26 |
27 | 28 |
29 | 30 | 31 |
32 |
33 | 34 |
35 |
36 | 37 |
38 |
39 | 40 |
41 | 42 |
43 |
44 | 45 |
46 | 47 |
48 |
49 | 50 |
51 | 52 |
53 |
54 | 55 |
56 |
57 | 58 | 59 | 60 |
61 |
62 | 63 | 64 |
65 |
66 | 67 |
68 |
69 | 70 | {% endblock %} 71 | 72 | {% block script %} 73 | 74 | {% endblock %} -------------------------------------------------------------------------------- /project3/mail/templates/mail/layout.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | 6 | {% block title %}Mail{% endblock %} 7 | 8 | 9 | {% block script %} 10 | {% endblock %} 11 | 12 | 13 |
14 | {% block body %} 15 | {% endblock %} 16 |
17 | 18 | -------------------------------------------------------------------------------- /project3/mail/templates/mail/login.html: -------------------------------------------------------------------------------- 1 | {% extends "mail/layout.html" %} 2 | 3 | {% block body %} 4 | 5 |

Login

6 | 7 | {% if message %} 8 |
{{ message }}
9 | {% endif %} 10 | 11 |
12 | {% csrf_token %} 13 |
14 | 15 |
16 |
17 | 18 |
19 | 20 |
21 | 22 | Don't have an account? Register here. 23 | 24 | {% endblock %} -------------------------------------------------------------------------------- /project3/mail/templates/mail/register.html: -------------------------------------------------------------------------------- 1 | {% extends "mail/layout.html" %} 2 | 3 | {% block body %} 4 | 5 |

Register

6 | 7 | {% if message %} 8 |
{{ message }}
9 | {% endif %} 10 | 11 |
12 | {% csrf_token %} 13 |
14 | 15 |
16 |
17 | 18 |
19 |
20 | 21 |
22 | 23 |
24 | 25 | Already have an account? Log In here. 26 | 27 | {% endblock %} -------------------------------------------------------------------------------- /project3/mail/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /project3/mail/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from . import views 4 | 5 | urlpatterns = [ 6 | path("", views.index, name="index"), 7 | path("login", views.login_view, name="login"), 8 | path("logout", views.logout_view, name="logout"), 9 | path("register", views.register, name="register"), 10 | 11 | # API Routes 12 | path("emails", views.compose, name="compose"), 13 | path("emails/", views.email, name="email"), 14 | path("emails/", views.mailbox, name="mailbox"), 15 | ] 16 | -------------------------------------------------------------------------------- /project3/mail/views.py: -------------------------------------------------------------------------------- 1 | import json 2 | from django.contrib.auth import authenticate, login, logout 3 | from django.contrib.auth.decorators import login_required 4 | from django.db import IntegrityError 5 | from django.http import JsonResponse 6 | from django.shortcuts import HttpResponse, HttpResponseRedirect, render 7 | from django.urls import reverse 8 | from django.views.decorators.csrf import csrf_exempt 9 | 10 | from .models import User, Email 11 | 12 | 13 | def index(request): 14 | 15 | # Authenticated users view their inbox 16 | if request.user.is_authenticated: 17 | return render(request, "mail/inbox.html") 18 | 19 | # Everyone else is prompted to sign in 20 | else: 21 | return HttpResponseRedirect(reverse("login")) 22 | 23 | 24 | @csrf_exempt 25 | @login_required 26 | def compose(request): 27 | 28 | # Composing a new email must be via POST 29 | if request.method != "POST": 30 | return JsonResponse({"error": "POST request required."}, status=400) 31 | 32 | # Check recipient emails 33 | data = json.loads(request.body) 34 | emails = [email.strip() for email in data.get("recipients").split(",")] 35 | if emails == [""]: 36 | return JsonResponse({ 37 | "error": "At least one recipient required." 38 | }, status=400) 39 | 40 | # Convert email addresses to users 41 | recipients = [] 42 | for email in emails: 43 | try: 44 | user = User.objects.get(email=email) 45 | recipients.append(user) 46 | except User.DoesNotExist: 47 | return JsonResponse({ 48 | "error": f"User with email {email} does not exist." 49 | }, status=400) 50 | 51 | # Get contents of email 52 | subject = data.get("subject", "") 53 | body = data.get("body", "") 54 | 55 | # Create one email for each recipient, plus sender 56 | users = set() 57 | users.add(request.user) 58 | users.update(recipients) 59 | for user in users: 60 | email = Email( 61 | user=user, 62 | sender=request.user, 63 | subject=subject, 64 | body=body, 65 | read=user == request.user 66 | ) 67 | email.save() 68 | for recipient in recipients: 69 | email.recipients.add(recipient) 70 | email.save() 71 | 72 | return JsonResponse({"message": "Email sent successfully."}, status=201) 73 | 74 | 75 | @login_required 76 | def mailbox(request, mailbox): 77 | 78 | # Filter emails returned based on mailbox 79 | if mailbox == "inbox": 80 | emails = Email.objects.filter( 81 | user=request.user, recipients=request.user, archived=False 82 | ) 83 | elif mailbox == "sent": 84 | emails = Email.objects.filter( 85 | user=request.user, sender=request.user 86 | ) 87 | elif mailbox == "archive": 88 | emails = Email.objects.filter( 89 | user=request.user, recipients=request.user, archived=True 90 | ) 91 | else: 92 | return JsonResponse({"error": "Invalid mailbox."}, status=400) 93 | 94 | # Return emails in reverse chronologial order 95 | emails = emails.order_by("-timestamp").all() 96 | return JsonResponse([email.serialize() for email in emails], safe=False) 97 | 98 | 99 | @csrf_exempt 100 | @login_required 101 | def email(request, email_id): 102 | 103 | # Query for requested email 104 | try: 105 | email = Email.objects.get(user=request.user, pk=email_id) 106 | except Email.DoesNotExist: 107 | return JsonResponse({"error": "Email not found."}, status=404) 108 | 109 | # Return email contents 110 | if request.method == "GET": 111 | return JsonResponse(email.serialize()) 112 | 113 | # Update whether email is read or should be archived 114 | elif request.method == "PUT": 115 | data = json.loads(request.body) 116 | if data.get("read") is not None: 117 | email.read = data["read"] 118 | if data.get("archived") is not None: 119 | email.archived = data["archived"] 120 | email.save() 121 | return HttpResponse(status=204) 122 | 123 | # Email must be via GET or PUT 124 | else: 125 | return JsonResponse({ 126 | "error": "GET or PUT request required." 127 | }, status=400) 128 | 129 | 130 | def login_view(request): 131 | if request.method == "POST": 132 | 133 | # Attempt to sign user in 134 | email = request.POST["email"] 135 | password = request.POST["password"] 136 | user = authenticate(request, username=email, password=password) 137 | 138 | # Check if authentication successful 139 | if user is not None: 140 | login(request, user) 141 | return HttpResponseRedirect(reverse("index")) 142 | else: 143 | return render(request, "mail/login.html", { 144 | "message": "Invalid email and/or password." 145 | }) 146 | else: 147 | return render(request, "mail/login.html") 148 | 149 | 150 | def logout_view(request): 151 | logout(request) 152 | return HttpResponseRedirect(reverse("index")) 153 | 154 | 155 | def register(request): 156 | if request.method == "POST": 157 | email = request.POST["email"] 158 | 159 | # Ensure password matches confirmation 160 | password = request.POST["password"] 161 | confirmation = request.POST["confirmation"] 162 | if password != confirmation: 163 | return render(request, "mail/register.html", { 164 | "message": "Passwords must match." 165 | }) 166 | 167 | # Attempt to create new user 168 | try: 169 | user = User.objects.create_user(email, email, password) 170 | user.save() 171 | except IntegrityError as e: 172 | print(e) 173 | return render(request, "mail/register.html", { 174 | "message": "Email address already taken." 175 | }) 176 | login(request, user) 177 | return HttpResponseRedirect(reverse("index")) 178 | else: 179 | return render(request, "mail/register.html") 180 | -------------------------------------------------------------------------------- /project3/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 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'project3.settings') 9 | try: 10 | from django.core.management import execute_from_command_line 11 | except ImportError as exc: 12 | raise ImportError( 13 | "Couldn't import Django. Are you sure it's installed and " 14 | "available on your PYTHONPATH environment variable? Did you " 15 | "forget to activate a virtual environment?" 16 | ) from exc 17 | execute_from_command_line(sys.argv) 18 | 19 | 20 | if __name__ == '__main__': 21 | main() 22 | -------------------------------------------------------------------------------- /project3/project3/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbsth/cs50w/90b58e0342ac3813fe0ee97b3191bd22836901fe/project3/project3/__init__.py -------------------------------------------------------------------------------- /project3/project3/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for project3 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.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', 'project3.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /project3/project3/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for project3 project. 3 | 4 | Generated by 'django-admin startproject' using Django 3.0.2. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.0/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/3.0/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = '05$4$3aew(8ywondz$g!k4m779pbvn9)euj0zp7-ae*x@4pxr+' 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 | 'mail', 35 | 'django.contrib.admin', 36 | 'django.contrib.auth', 37 | 'django.contrib.contenttypes', 38 | 'django.contrib.sessions', 39 | 'django.contrib.messages', 40 | 'django.contrib.staticfiles', 41 | ] 42 | 43 | MIDDLEWARE = [ 44 | 'django.middleware.security.SecurityMiddleware', 45 | 'django.contrib.sessions.middleware.SessionMiddleware', 46 | 'django.middleware.common.CommonMiddleware', 47 | 'django.middleware.csrf.CsrfViewMiddleware', 48 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 49 | 'django.contrib.messages.middleware.MessageMiddleware', 50 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 51 | ] 52 | 53 | ROOT_URLCONF = 'project3.urls' 54 | 55 | TEMPLATES = [ 56 | { 57 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 58 | 'DIRS': [], 59 | 'APP_DIRS': True, 60 | 'OPTIONS': { 61 | 'context_processors': [ 62 | 'django.template.context_processors.debug', 63 | 'django.template.context_processors.request', 64 | 'django.contrib.auth.context_processors.auth', 65 | 'django.contrib.messages.context_processors.messages', 66 | ], 67 | }, 68 | }, 69 | ] 70 | 71 | WSGI_APPLICATION = 'project3.wsgi.application' 72 | 73 | 74 | # Database 75 | # https://docs.djangoproject.com/en/3.0/ref/settings/#databases 76 | 77 | DATABASES = { 78 | 'default': { 79 | 'ENGINE': 'django.db.backends.sqlite3', 80 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 81 | } 82 | } 83 | 84 | AUTH_USER_MODEL = 'mail.User' 85 | 86 | # Password validation 87 | # https://docs.djangoproject.com/en/3.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/3.0/topics/i18n/ 107 | 108 | LANGUAGE_CODE = 'en-us' 109 | 110 | TIME_ZONE = 'UTC' 111 | 112 | USE_I18N = True 113 | 114 | USE_L10N = True 115 | 116 | USE_TZ = True 117 | 118 | 119 | # Static files (CSS, JavaScript, Images) 120 | # https://docs.djangoproject.com/en/3.0/howto/static-files/ 121 | 122 | STATIC_URL = '/static/' 123 | -------------------------------------------------------------------------------- /project3/project3/urls.py: -------------------------------------------------------------------------------- 1 | """project3 URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/3.0/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.contrib import admin 17 | from django.urls import include, path 18 | 19 | urlpatterns = [ 20 | path('admin/', admin.site.urls), 21 | path('', include('mail.urls')) 22 | ] 23 | -------------------------------------------------------------------------------- /project3/project3/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for project3 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.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', 'project3.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /project4/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 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'project4.settings') 9 | try: 10 | from django.core.management import execute_from_command_line 11 | except ImportError as exc: 12 | raise ImportError( 13 | "Couldn't import Django. Are you sure it's installed and " 14 | "available on your PYTHONPATH environment variable? Did you " 15 | "forget to activate a virtual environment?" 16 | ) from exc 17 | execute_from_command_line(sys.argv) 18 | 19 | 20 | if __name__ == '__main__': 21 | main() 22 | -------------------------------------------------------------------------------- /project4/network/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbsth/cs50w/90b58e0342ac3813fe0ee97b3191bd22836901fe/project4/network/__init__.py -------------------------------------------------------------------------------- /project4/network/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import User, Post 3 | 4 | 5 | # Register your models here. 6 | class PostAdmin(admin.ModelAdmin): 7 | fields = ["content"] 8 | 9 | 10 | admin.site.register(Post, PostAdmin) 11 | -------------------------------------------------------------------------------- /project4/network/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class NetworkConfig(AppConfig): 5 | name = 'network' 6 | -------------------------------------------------------------------------------- /project4/network/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2 on 2021-08-01 18:55 2 | 3 | import django.contrib.auth.models 4 | import django.contrib.auth.validators 5 | from django.db import migrations, models 6 | import django.utils.timezone 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='User', 20 | fields=[ 21 | ('id', models.AutoField(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 | ('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')), 33 | ('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')), 34 | ], 35 | options={ 36 | 'verbose_name': 'user', 37 | 'verbose_name_plural': 'users', 38 | 'abstract': False, 39 | }, 40 | managers=[ 41 | ('objects', django.contrib.auth.models.UserManager()), 42 | ], 43 | ), 44 | ] 45 | -------------------------------------------------------------------------------- /project4/network/migrations/0002_post.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.5 on 2021-08-08 15:36 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('network', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='Post', 17 | fields=[ 18 | ('id', models.AutoField(primary_key=True, serialize=False)), 19 | ('addedOn', models.DateTimeField(auto_now_add=True)), 20 | ('content', models.CharField(max_length=3000)), 21 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 22 | ], 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /project4/network/migrations/0003_alter_post_options.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2 on 2021-08-10 16:35 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('network', '0002_post'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name='post', 15 | options={'ordering': ['addedOn']}, 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /project4/network/migrations/0004_alter_post_options.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2 on 2021-08-10 16:35 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('network', '0003_alter_post_options'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name='post', 15 | options={'ordering': ['-addedOn']}, 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /project4/network/migrations/0005_user_followers.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2 on 2021-08-10 17:52 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('network', '0004_alter_post_options'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='user', 16 | name='followers', 17 | field=models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /project4/network/migrations/0006_auto_20210920_2224.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2 on 2021-09-20 20:24 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('network', '0005_user_followers'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='post', 16 | name='liked_by', 17 | field=models.ManyToManyField(blank=True, related_name='users_liked', to=settings.AUTH_USER_MODEL), 18 | ), 19 | migrations.AlterField( 20 | model_name='user', 21 | name='followers', 22 | field=models.ManyToManyField(blank=True, related_name='user_followers', to=settings.AUTH_USER_MODEL), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /project4/network/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbsth/cs50w/90b58e0342ac3813fe0ee97b3191bd22836901fe/project4/network/migrations/__init__.py -------------------------------------------------------------------------------- /project4/network/models.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import AbstractUser 2 | from django.db import models 3 | 4 | 5 | class User(AbstractUser): 6 | followers = models.ManyToManyField('self', symmetrical=False, blank=True, related_name='user_followers') 7 | 8 | def count_followers(self): 9 | return self.followers.count() 10 | 11 | def count_following(self): 12 | return User.objects.filter(followers=self).count() 13 | 14 | 15 | class Post(models.Model): 16 | id = models.AutoField(primary_key=True) 17 | user = models.ForeignKey(User, on_delete=models.CASCADE) 18 | addedOn = models.DateTimeField(auto_now_add=True) 19 | content = models.CharField(max_length=3000) 20 | liked_by = models.ManyToManyField(User, symmetrical=False, blank=True, related_name="users_liked") 21 | 22 | def count_likes(self): 23 | return self.liked_by.count() 24 | 25 | class Meta: 26 | ordering = ['-addedOn'] 27 | -------------------------------------------------------------------------------- /project4/network/static/network/index-page.js: -------------------------------------------------------------------------------- 1 | document.addEventListener('DOMContentLoaded', function() { 2 | prepare_posts(); 3 | }); 4 | 5 | function prepare_posts(){ 6 | let posts = document.querySelectorAll("div.post-div"); 7 | posts.forEach((input) => { 8 | const edit_link = input.querySelector("span.edit-label"); 9 | edit_link.addEventListener('click', ()=>edit_post(input)); 10 | 11 | const like_label = input.querySelector("span.like-count-label") 12 | like_label.addEventListener('click', ()=>like_post_click(input)); 13 | }); 14 | } 15 | 16 | function edit_post(post){ 17 | let post_text_div = post.querySelector("div.post-context") 18 | let edit_text_div = post.querySelector("span.edit-label") 19 | let post_id = post.querySelector("input.post-id").value 20 | let original_html = post_text_div.innerHTML 21 | let original_post = post_text_div.innerText 22 | edit_text_div.style.display = "none" 23 | 24 | const edit_div = document.createElement('div'); 25 | let new_html = ` 26 |
27 |
28 | 29 |
30 |
31 |
Cancel
32 |
33 |
34 |
` 35 | 36 | post_text_div.innerHTML = new_html; 37 | 38 | let cancelEditLabel = post_text_div.querySelector("span.cancel-edit-label") 39 | let sendEditButton = post_text_div.querySelector("button") 40 | 41 | cancelEditLabel.addEventListener('click', ()=>cancelEdit()) 42 | sendEditButton.addEventListener('click', ()=>sendEdit()) 43 | 44 | function cancelEdit() 45 | { 46 | post_text_div.innerHTML = original_html 47 | edit_text_div.style.display="block" 48 | } 49 | 50 | function sendEdit() 51 | { 52 | let new_text = post_text_div.querySelector("textarea.edited-post").value 53 | const csrftoken = getCookie('csrftoken'); 54 | 55 | const request = new Request( 56 | `/post/${post_id}/edit`, 57 | {headers: {'X-CSRFToken': csrftoken}} 58 | ); 59 | 60 | fetch(request, { 61 | method: 'POST', 62 | body: JSON.stringify({ 63 | edited_text: new_text, 64 | }) 65 | }).then(response => { 66 | if(response.status === 200){ 67 | restoreView(new_text); 68 | } 69 | else{ 70 | restoreView(original_post) 71 | } 72 | 73 | }); 74 | } 75 | 76 | function restoreView(postContent){ 77 | post_text_div.innerHTML = original_html; 78 | post_text_div.innerText = postContent; 79 | edit_text_div.style.display="block"; 80 | } 81 | } 82 | 83 | function like_post_click(post) { 84 | console.log("CLICK") 85 | let post_id = post.querySelector("input.post-id").value; 86 | 87 | const csrftoken = getCookie('csrftoken'); 88 | const like_label = post.querySelector("span.like-count-label") 89 | 90 | let like_action = 'like'; 91 | 92 | const request = new Request( 93 | `/post/${post_id}/like`, 94 | {headers: {'X-CSRFToken': csrftoken}}); 95 | 96 | fetch(request, { 97 | method: 'POST', 98 | body: JSON.stringify({ 99 | action: like_action, 100 | }) 101 | }).then(response => response.json()) 102 | .then(data=>{ 103 | handle_like_count(data["like_count"], data["like_status"]); 104 | }); 105 | 106 | function handle_like_count(like_count, like_status){ 107 | like_label.innerText = like_count; 108 | //console.log(like_label.classList); 109 | if(like_status){ 110 | like_label.classList.add("liked-post") 111 | } 112 | else{ 113 | like_label.classList.remove("liked-post") 114 | } 115 | } 116 | 117 | } 118 | 119 | function getCookie(name) { 120 | let cookieValue = null; 121 | if (document.cookie && document.cookie !== '') { 122 | const cookies = document.cookie.split(';'); 123 | for (let i = 0; i < cookies.length; i++) { 124 | const cookie = cookies[i].trim(); 125 | // Does this cookie string begin with the name we want? 126 | if (cookie.substring(0, name.length + 1) === (name + '=')) { 127 | cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); 128 | break; 129 | } 130 | } 131 | } 132 | return cookieValue; 133 | } 134 | -------------------------------------------------------------------------------- /project4/network/static/network/styles.css: -------------------------------------------------------------------------------- 1 | textarea{ 2 | height: 100px; 3 | } 4 | 5 | .post-author{ 6 | 7 | } 8 | 9 | .post-content{ 10 | 11 | } 12 | 13 | .post-date{ 14 | font-size: 12px; 15 | color:gray; 16 | } 17 | 18 | .edit-label, 19 | .cancel-edit-label { 20 | color:dodgerblue; 21 | } 22 | 23 | .like-count-label, 24 | .edit-label:hover, 25 | .cancel-edit-label:hover{ 26 | cursor: pointer; 27 | } 28 | 29 | .liked-post{ 30 | font-weight: bold; 31 | } -------------------------------------------------------------------------------- /project4/network/templates/network/following.html: -------------------------------------------------------------------------------- 1 | {% extends "network/layout.html" %} 2 | 3 | {% block body %} 4 |
5 |

Posts by followed users:

6 |
7 | 8 | {% for post in posts %} 9 |
10 |
11 | 12 |
13 |
14 |
{{ post.content }}
15 |
16 |
17 | 18 |
19 |
20 |
0 ❤
21 |
22 |
23 | {% endfor %} 24 | {% endblock %} -------------------------------------------------------------------------------- /project4/network/templates/network/index.html: -------------------------------------------------------------------------------- 1 | {% extends "network/layout.html" %} 2 | {% load static %} 3 | 4 | {% block body %} 5 | 6 |
7 |

All posts:

8 |
9 | 10 | {% if user.is_authenticated %} 11 |
12 |
13 |
14 | New post: 15 |
16 |
17 | 18 |
19 |
20 |
21 | {% csrf_token %} 22 |
23 | 24 |
25 | 26 |
27 |
28 |
29 | 30 |
31 | {% endif %} 32 | 33 | {% for post in current_page.object_list %} 34 |
35 | 36 |
37 | 38 |
39 | {% if user.is_authenticated and post.user == user %} 40 |
41 |
edit
42 |
43 | {% endif %} 44 |
45 |
{{ post.content }}
46 |
47 |
48 | 49 |
50 |
51 |
52 |
53 |
54 | {% endfor %} 55 | 56 | {% if pagination_needed %} 57 |
58 | 68 |
69 | {% endif %} 70 | 71 | {% endblock %} 72 | 73 | {% block script %} 74 | 75 | {% endblock script %} -------------------------------------------------------------------------------- /project4/network/templates/network/layout.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | 6 | {% block title %}Social Network{% endblock %} 7 | 8 | 9 | {% block script %} 10 | {% endblock %} 11 | 12 | 13 | 14 | 45 | 46 |
47 | {% block body %} 48 | {% endblock %} 49 |
50 | 51 | -------------------------------------------------------------------------------- /project4/network/templates/network/login.html: -------------------------------------------------------------------------------- 1 | {% extends "network/layout.html" %} 2 | 3 | {% block body %} 4 | 5 |

Login

6 | 7 | {% if message %} 8 |
{{ message }}
9 | {% endif %} 10 | 11 |
12 | {% csrf_token %} 13 |
14 | 15 |
16 |
17 | 18 |
19 | 20 |
21 | 22 | Don't have an account? Register here. 23 | 24 | {% endblock %} -------------------------------------------------------------------------------- /project4/network/templates/network/register.html: -------------------------------------------------------------------------------- 1 | {% extends "network/layout.html" %} 2 | 3 | {% block body %} 4 | 5 |

Register

6 | 7 | {% if message %} 8 |
{{ message }}
9 | {% endif %} 10 | 11 |
12 | {% csrf_token %} 13 |
14 | 15 |
16 |
17 | 18 |
19 |
20 | 21 |
22 |
23 | 24 |
25 | 26 |
27 | 28 | Already have an account? Log In here. 29 | 30 | {% endblock %} -------------------------------------------------------------------------------- /project4/network/templates/network/user_view.html: -------------------------------------------------------------------------------- 1 | {% extends "network/index.html" %} 2 | 3 | {% block body %} 4 |
5 |
6 |

{{ user_name }}

7 |
8 | {% if follow_button_visible %} 9 |
10 | {% csrf_token %} 11 | 12 |
13 | {% endif %} 14 |
15 |
16 | 17 |
18 |
following: {{ following }}
19 |
followers: {{ followers }}
20 |
21 | 22 |
23 |
24 | {% for post in posts %} 25 |
26 |
27 |

added on: {{ post.addedOn }}

28 |

{{ post.content }}

29 |
30 |
31 | {% endfor %} 32 |
33 |
34 |
35 | {# display number of the followers, and number of following #} 36 | {# display all user posts #} 37 | {# follow/unfollow button, active only for other users #} 38 | {% endblock %} -------------------------------------------------------------------------------- /project4/network/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /project4/network/urls.py: -------------------------------------------------------------------------------- 1 | 2 | from django.urls import path 3 | 4 | from . import views 5 | 6 | urlpatterns = [ 7 | path("", views.index, name="index"), 8 | path("following", views.following, name="following"), 9 | path("login", views.login_view, name="login"), 10 | path("logout", views.logout_view, name="logout"), 11 | path("register", views.register, name="register"), 12 | path("user/", views.user_view, name="user_page"), 13 | path("post//edit", views.post_edit, name="post_edit"), 14 | path("post//like", views.like_post, name="like_post") 15 | ] 16 | -------------------------------------------------------------------------------- /project4/network/views.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import authenticate, login, logout 2 | from django.db import IntegrityError 3 | from django.http import HttpResponse, HttpResponseRedirect, JsonResponse 4 | from django.shortcuts import render 5 | from django.urls import reverse 6 | from django.core.paginator import Paginator 7 | import json 8 | 9 | from .models import User, Post 10 | 11 | 12 | def index(request): 13 | posts_per_page = 10 14 | post_list = Post.objects.all() 15 | 16 | paginator = Paginator(post_list, posts_per_page) 17 | get_page = request.GET.get('p') 18 | page_number = get_page if get_page is not None else 1 19 | current_page = paginator.get_page(page_number) 20 | 21 | context = { 22 | "current_page": current_page, 23 | "pagination_needed": paginator.num_pages > 1 24 | } 25 | 26 | if request.method == "POST": 27 | # attempt to send new post 28 | post_body = request.POST["post-text"] 29 | 30 | if len(post_body) > 0: 31 | post = Post(user=request.user, content=post_body) 32 | post.save() 33 | 34 | return render(request, "network/index.html", context) 35 | 36 | 37 | def login_view(request): 38 | if request.method == "POST": 39 | 40 | # Attempt to sign user in 41 | username = request.POST["username"] 42 | password = request.POST["password"] 43 | user = authenticate(request, username=username, password=password) 44 | 45 | # Check if authentication successful 46 | if user is not None: 47 | login(request, user) 48 | return HttpResponseRedirect(reverse("index")) 49 | else: 50 | return render(request, "network/login.html", { 51 | "message": "Invalid username and/or password." 52 | }) 53 | else: 54 | return render(request, "network/login.html") 55 | 56 | 57 | def logout_view(request): 58 | logout(request) 59 | return HttpResponseRedirect(reverse("index")) 60 | 61 | 62 | def register(request): 63 | if request.method == "POST": 64 | username = request.POST["username"] 65 | email = request.POST["email"] 66 | 67 | # Ensure password matches confirmation 68 | password = request.POST["password"] 69 | confirmation = request.POST["confirmation"] 70 | if password != confirmation: 71 | return render(request, "network/register.html", { 72 | "message": "Passwords must match." 73 | }) 74 | 75 | # Attempt to create new user 76 | try: 77 | user = User.objects.create_user(username, email, password) 78 | user.save() 79 | except IntegrityError: 80 | return render(request, "network/register.html", { 81 | "message": "Username already taken." 82 | }) 83 | login(request, user) 84 | return HttpResponseRedirect(reverse("index")) 85 | else: 86 | return render(request, "network/register.html") 87 | 88 | 89 | def user_view(request, user_name): 90 | user_profile = User.objects.get(username=user_name) 91 | current_user = request.user 92 | 93 | if request.method == "POST": 94 | # check the status 95 | status = request.POST['follow-button'] 96 | if status == 'Unfollow': 97 | user_profile.followers.remove(current_user) 98 | elif status == 'Follow': 99 | user_profile.followers.add(current_user) 100 | pass 101 | 102 | # determine follow button visibility 103 | follow_button_visibility = request.user.is_authenticated and request.user.username != user_name 104 | 105 | # determine follow button text 106 | if current_user in user_profile.followers.all(): 107 | follow_text = 'Unfollow' 108 | else: 109 | follow_text = 'Follow' 110 | 111 | # determine followers and follower count 112 | followers_count = user_profile.count_followers() 113 | following_count = user_profile.count_following() 114 | 115 | context = { 116 | 'user_name': user_name, 117 | 'following': following_count, 118 | 'followers': followers_count, 119 | 'posts': Post.objects.filter(user__username=user_name), 120 | 'follow_button_visible': follow_button_visibility, 121 | 'follow_text': follow_text 122 | } 123 | 124 | return render(request, 'network/user_view.html', context) 125 | 126 | 127 | def following(request): 128 | followed_users = User.objects.filter(followers=request.user) 129 | posts_by_followed = Post.objects.filter(user__in=followed_users) 130 | 131 | context = { 132 | "posts": posts_by_followed 133 | } 134 | 135 | return render(request, "network/following.html", context) 136 | 137 | 138 | def post_edit(request, post_id): 139 | if request.method == "POST": 140 | # get post with that id 141 | post = Post.objects.filter(id=post_id) 142 | 143 | # check if that post exist 144 | if len(post) == 1: 145 | post_to_edit = post[0] 146 | read_json = json.loads(request.body) 147 | post_to_edit.content = read_json['edited_text'] 148 | post_to_edit.save() 149 | return HttpResponse(200) 150 | 151 | return HttpResponse(404) 152 | 153 | 154 | def like_post(request, post_id): 155 | if request.method == "POST": 156 | # get post with that id 157 | post = Post.objects.filter(id=post_id) 158 | 159 | # check if that post exist 160 | if len(post) == 1: 161 | post_to_edit = post[0] 162 | read_json = json.loads(request.body) 163 | # determine if post is to be liked or unlike 164 | action = read_json['action'] 165 | 166 | # check if post is liked by requesting user 167 | post_is_liked = request.user in post_to_edit.liked_by.all() 168 | 169 | if action == "like" and not post_is_liked: 170 | post_to_edit.liked_by.add(request.user) 171 | post_to_edit.save() 172 | elif post_is_liked: 173 | post_to_edit.liked_by.remove(request.user) 174 | post_to_edit.save() 175 | 176 | like_count = post_to_edit.count_likes() 177 | like_status = request.user in post_to_edit.liked_by.all() 178 | 179 | response = {'like_count': like_count, 180 | 'like_status': like_status} 181 | 182 | return JsonResponse(response) 183 | 184 | return HttpResponse(404) 185 | -------------------------------------------------------------------------------- /project4/project4/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbsth/cs50w/90b58e0342ac3813fe0ee97b3191bd22836901fe/project4/project4/__init__.py -------------------------------------------------------------------------------- /project4/project4/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for project4 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.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', 'project4.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /project4/project4/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for project4 project. 3 | 4 | Generated by 'django-admin startproject' using Django 3.0.2. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.0/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/3.0/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = '13kl@xtukpwe&xj2xoysxe9_6=tf@f8ewxer5n&ifnd46+6$%8' 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 | 'network', 35 | 'django.contrib.admin', 36 | 'django.contrib.auth', 37 | 'django.contrib.contenttypes', 38 | 'django.contrib.sessions', 39 | 'django.contrib.messages', 40 | 'django.contrib.staticfiles', 41 | ] 42 | 43 | MIDDLEWARE = [ 44 | 'django.middleware.security.SecurityMiddleware', 45 | 'django.contrib.sessions.middleware.SessionMiddleware', 46 | 'django.middleware.common.CommonMiddleware', 47 | 'django.middleware.csrf.CsrfViewMiddleware', 48 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 49 | 'django.contrib.messages.middleware.MessageMiddleware', 50 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 51 | ] 52 | 53 | ROOT_URLCONF = 'project4.urls' 54 | 55 | TEMPLATES = [ 56 | { 57 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 58 | 'DIRS': [], 59 | 'APP_DIRS': True, 60 | 'OPTIONS': { 61 | 'context_processors': [ 62 | 'django.template.context_processors.debug', 63 | 'django.template.context_processors.request', 64 | 'django.contrib.auth.context_processors.auth', 65 | 'django.contrib.messages.context_processors.messages', 66 | ], 67 | }, 68 | }, 69 | ] 70 | 71 | WSGI_APPLICATION = 'project4.wsgi.application' 72 | 73 | 74 | # Database 75 | # https://docs.djangoproject.com/en/3.0/ref/settings/#databases 76 | 77 | DATABASES = { 78 | 'default': { 79 | 'ENGINE': 'django.db.backends.sqlite3', 80 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 81 | } 82 | } 83 | 84 | AUTH_USER_MODEL = "network.User" 85 | 86 | # Password validation 87 | # https://docs.djangoproject.com/en/3.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/3.0/topics/i18n/ 107 | 108 | LANGUAGE_CODE = 'en-us' 109 | 110 | TIME_ZONE = 'UTC' 111 | 112 | USE_I18N = True 113 | 114 | USE_L10N = True 115 | 116 | USE_TZ = True 117 | 118 | 119 | # Static files (CSS, JavaScript, Images) 120 | # https://docs.djangoproject.com/en/3.0/howto/static-files/ 121 | 122 | STATIC_URL = '/static/' 123 | -------------------------------------------------------------------------------- /project4/project4/urls.py: -------------------------------------------------------------------------------- 1 | """project4 URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/3.0/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.contrib import admin 17 | from django.urls import include, path 18 | 19 | urlpatterns = [ 20 | path("admin/", admin.site.urls), 21 | path("", include("network.urls")), 22 | ] 23 | -------------------------------------------------------------------------------- /project4/project4/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for project4 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.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', 'project4.settings') 15 | 16 | application = get_wsgi_application() 17 | --------------------------------------------------------------------------------