├── .cursorrules ├── .gitignore ├── LICENSE ├── README.md ├── config ├── cursor-django.service └── nginx.conf ├── cursor-django.code-workspace ├── requirements.txt └── src ├── blog ├── __init__.py ├── admin.py ├── apps.py ├── forms.py ├── management │ └── commands │ │ └── create_blog_posts.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_article_is_published_alter_article_pub_date.py │ └── __init__.py ├── models.py ├── tests.py ├── urls.py └── views.py ├── cfehome ├── __init__.py ├── asgi.py ├── forms.py ├── settings.py ├── urls.py ├── views.py └── wsgi.py ├── comments ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ ├── 0001_initial.py │ └── __init__.py ├── models.py ├── tests.py └── views.py ├── contact ├── __init__.py ├── admin.py ├── apps.py ├── forms.py ├── migrations │ ├── 0001_initial.py │ └── __init__.py ├── models.py ├── tests.py └── views.py ├── manage.py ├── staticfiles └── css │ └── flowbite.min.css └── templates ├── about.html ├── base.html ├── blog ├── article_detail.html └── article_list.html ├── contact.html ├── contact └── contact_form.html ├── dashboard └── user_dashboard.html ├── home.html ├── login.html ├── pricing.html ├── product_detail.html ├── products.html ├── signup.html └── tech_stack.html /.cursorrules: -------------------------------------------------------------------------------- 1 | Environment: 2 | - 1 CPU Core 3 | - 25 GB Storage 4 | - 1 GB RAM 5 | - 0 Volumes 6 | - OS: Ubuntu 24.04 LTS 7 | - new pacakges install - auto approve 8 | - Update system rarely 9 | 10 | # My Info 11 | - name: justin mitchel 12 | - email: codingforentrepreneurs@gmail.com 13 | 14 | # Project 15 | - Repo url: https://github.com/codingforentrepreneurs/Cursor-Django 16 | - Root folder: /opt/cursor/projects/cursor-django 17 | - Runtime: Python 3.12 18 | - Python Environment: `venv` at `/opt/cursor/projects/cursor-django/venv` 19 | - Django root folder: `/opt/cursor/projects/cursor-django/src` 20 | - Django project name: `cfehome` 21 | - Django run command: `venv/bin/gunicorn --chdir src cfehome.wsgi:application --bind 0.0.0.0:8000 --reload --log-level debug` 22 | - Django background service: `cursor-django.service` 23 | - Environment-variables manager: `python-decouple` 24 | - Django Static root: `src/static` 25 | - Django Static url: `/static/` 26 | - Django Static dirs: `src/staticfiles` -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | src/static/ 2 | 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | share/python-wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | MANIFEST 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .nox/ 45 | .coverage 46 | .coverage.* 47 | .cache 48 | nosetests.xml 49 | coverage.xml 50 | *.cover 51 | *.py,cover 52 | .hypothesis/ 53 | .pytest_cache/ 54 | cover/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | db.sqlite3-journal 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | .pybuilder/ 78 | target/ 79 | 80 | # Jupyter Notebook 81 | .ipynb_checkpoints 82 | 83 | # IPython 84 | profile_default/ 85 | ipython_config.py 86 | 87 | # pyenv 88 | # For a library or package, you might want to ignore these files since the code is 89 | # intended to run in multiple environments; otherwise, check them in: 90 | # .python-version 91 | 92 | # pipenv 93 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 94 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 95 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 96 | # install all needed dependencies. 97 | #Pipfile.lock 98 | 99 | # poetry 100 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 101 | # This is especially recommended for binary packages to ensure reproducibility, and is more 102 | # commonly ignored for libraries. 103 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 104 | #poetry.lock 105 | 106 | # pdm 107 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 108 | #pdm.lock 109 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 110 | # in version control. 111 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control 112 | .pdm.toml 113 | .pdm-python 114 | .pdm-build/ 115 | 116 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 117 | __pypackages__/ 118 | 119 | # Celery stuff 120 | celerybeat-schedule 121 | celerybeat.pid 122 | 123 | # SageMath parsed files 124 | *.sage.py 125 | 126 | # Environments 127 | .env 128 | .venv 129 | env/ 130 | venv/ 131 | ENV/ 132 | env.bak/ 133 | venv.bak/ 134 | 135 | # Spyder project settings 136 | .spyderproject 137 | .spyproject 138 | 139 | # Rope project settings 140 | .ropeproject 141 | 142 | # mkdocs documentation 143 | /site 144 | 145 | # mypy 146 | .mypy_cache/ 147 | .dmypy.json 148 | dmypy.json 149 | 150 | # Pyre type checker 151 | .pyre/ 152 | 153 | # pytype static type analyzer 154 | .pytype/ 155 | 156 | # Cython debug symbols 157 | cython_debug/ 158 | 159 | # PyCharm 160 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 161 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 162 | # and can be added to the global gitignore or merged into this file. For a more nuclear 163 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 164 | #.idea/ 165 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Coding For Entrepreneurs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Learn how to build Python-based Web Apps with Cursor AI 2 | 3 | This repo is the output from the [YouTube course](https://youtu.be/ESrWYoXZc8s) building a full-stack web application with English. 4 | 5 | Create a cutting edge and modern web app using just English. Cursor AI unlocks an amazing ability to build fast and robust applications by allowing you to speak English while it writes the code for you in a code-editing environment. 6 | 7 | While tools like ChatGPT and Claude have websites that make it easy to copy and paste code generated, Cursor brings these tools right next to your code for the most amazing auto-complete tool you've ever used -- far better than anything on the market yet. The best part? A lot of it costs $0 to get started for a _long time_. This entire course cost $0 in AI costs. 8 | 9 | What we build in this course used to cost thousands (if not tens of thousands) of dollars to build over the course of a month or two. You'll do it for free and in a little over 5 hours. 10 | 11 | Cursor is what you thought GitHub Copilot was going to be. Heck, even if they do the same thing by the time you watch this video, you can thank Cursor for pushing the envelop and helping beginners and professional developers a like. 12 | 13 | Let's do this! Topics, code links, and chapters below. 14 | 15 | Topics: 16 | 17 | ✅ Do Full Stack Web Development in pure English 18 | 19 | ✅ Leverage Anthropic's Claude.ai right on your machine without copying and pasting 20 | 21 | ✅ Diagnose issues with ease 22 | 23 | ✅ Learn various commands to speed up how you use Cursor AI 24 | 25 | ✅ Create rules for Cursor to follow to ensure consistent workflows 26 | 27 | ✅ Skip local setup and develop directly on cheap servers 28 | 29 | ✅ Build your first static website in about 10 minutes 30 | 31 | ✅ Integrate Git and GitHub to easily backup your code and undo bad AI generations 32 | 33 | ✅ Build a Python & Django Web App without writing a single line of code 34 | 35 | ✅ Leverage Neon Postgres to persist database data across VMs 36 | 37 | ✅ Deploy a production-ready web server to access your in-development web app from anywhere in the world 38 | 39 | ✅ And much more! 40 | 41 | Watch it [on youtube](https://youtu.be/ESrWYoXZc8s) 42 | -------------------------------------------------------------------------------- /config/cursor-django.service: -------------------------------------------------------------------------------- 1 | 2 | # Installation and Usage Instructions: 3 | 4 | # 1. Copy this file to the systemd service directory: 5 | # sudo cp /opt/cursor/projects/cursor-django/config/cursor-django.service /etc/systemd/system/ 6 | 7 | # 2. Set the correct permissions: 8 | # sudo chmod 644 /etc/systemd/system/cursor-django.service 9 | 10 | # 3. Reload the systemd daemon to recognize the new service: 11 | # sudo systemctl daemon-reload 12 | 13 | # 4. Start the service: 14 | # sudo systemctl start cursor-django 15 | 16 | # 5. Enable the service to start on boot: 17 | # sudo systemctl enable cursor-django 18 | 19 | # 6. Check the status of the service: 20 | # sudo systemctl status cursor-django 21 | 22 | # 7. To stop the service: 23 | # sudo systemctl stop cursor-django 24 | 25 | # 8. To restart the service: 26 | # sudo systemctl restart cursor-django 27 | 28 | # 9. To view the logs: 29 | # sudo journalctl -u cursor-django 30 | 31 | 32 | [Unit] 33 | Description=Cursor Django Application 34 | After=network.target 35 | 36 | [Service] 37 | User=root 38 | Group=www-data 39 | WorkingDirectory=/opt/cursor/projects/cursor-django 40 | ExecStart=/opt/cursor/projects/cursor-django/venv/bin/gunicorn --chdir src cfehome.wsgi:application --bind 0.0.0.0:8000 --reload --log-level debug 41 | Restart=always 42 | 43 | [Install] 44 | WantedBy=multi-user.target 45 | -------------------------------------------------------------------------------- /config/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name _; # Allow any domain or IP address 4 | 5 | location = /favicon.ico { access_log off; log_not_found off; } 6 | location /static/ { 7 | root /opt/cursor/projects/cursor-django/src; 8 | } 9 | 10 | location / { 11 | proxy_pass http://127.0.0.1:8000; 12 | proxy_set_header Host $host; 13 | proxy_set_header X-Real-IP $remote_addr; 14 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 15 | proxy_set_header X-Forwarded-Proto $scheme; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /cursor-django.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ] 7 | } -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django 2 | python-decouple 3 | gunicorn 4 | psycopg[binary] 5 | dj-database-url 6 | faker -------------------------------------------------------------------------------- /src/blog/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/Cursor-Django/62068036fd145b8473920dbf910b42e5bf96d54a/src/blog/__init__.py -------------------------------------------------------------------------------- /src/blog/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Article 3 | 4 | class ArticleAdmin(admin.ModelAdmin): 5 | list_display = ('title', 'author', 'pub_date') 6 | list_filter = ['pub_date', 'author'] 7 | search_fields = ['title', 'content'] 8 | 9 | admin.site.register(Article, ArticleAdmin) 10 | -------------------------------------------------------------------------------- /src/blog/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class BlogConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'blog' 7 | -------------------------------------------------------------------------------- /src/blog/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from comments.models import Comment 3 | 4 | class CommentForm(forms.ModelForm): 5 | class Meta: 6 | model = Comment 7 | fields = ['text'] 8 | widgets = { 9 | 'text': forms.Textarea(attrs={'rows': 4, 'cols': 50}), 10 | } -------------------------------------------------------------------------------- /src/blog/management/commands/create_blog_posts.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | from django.contrib.auth.models import User 3 | from blog.models import Article 4 | from faker import Faker 5 | import random 6 | 7 | class Command(BaseCommand): 8 | help = 'Creates a specified number of realistic blog posts' 9 | 10 | def add_arguments(self, parser): 11 | parser.add_argument('total', type=int, help='Indicates the number of blog posts to be created') 12 | 13 | def handle(self, *args, **kwargs): 14 | total = kwargs['total'] 15 | fake = Faker() 16 | 17 | # Get all users 18 | users = User.objects.all() 19 | 20 | if not users: 21 | self.stdout.write(self.style.WARNING('No users found. Creating a default user.')) 22 | User.objects.create_user(username='default_user', password='password123') 23 | users = User.objects.all() 24 | 25 | for _ in range(total): 26 | title = fake.sentence(nb_words=6, variable_nb_words=True) 27 | content = '\n\n'.join(fake.paragraphs(nb=random.randint(3, 7))) 28 | is_published = random.choice([True, False]) 29 | author = random.choice(users) 30 | 31 | article = Article.objects.create( 32 | title=title, 33 | content=content, 34 | is_published=is_published, 35 | author=author 36 | ) 37 | 38 | status = "Published" if is_published else "Draft" 39 | self.stdout.write(self.style.SUCCESS(f'Successfully created article "{title}" ({status})')) 40 | 41 | self.stdout.write(self.style.SUCCESS(f'Successfully created {total} blog posts')) -------------------------------------------------------------------------------- /src/blog/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.1 on 2024-10-04 22:04 2 | 3 | import django.db.models.deletion 4 | from django.conf import settings 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='Article', 19 | fields=[ 20 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 21 | ('title', models.CharField(max_length=200)), 22 | ('content', models.TextField()), 23 | ('pub_date', models.DateTimeField(auto_now_add=True, verbose_name='date published')), 24 | ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 25 | ], 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /src/blog/migrations/0002_article_is_published_alter_article_pub_date.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.1 on 2024-10-04 22:08 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('blog', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='article', 15 | name='is_published', 16 | field=models.BooleanField(default=False, verbose_name='ready to publish'), 17 | ), 18 | migrations.AlterField( 19 | model_name='article', 20 | name='pub_date', 21 | field=models.DateTimeField(blank=True, null=True, verbose_name='date published'), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /src/blog/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/Cursor-Django/62068036fd145b8473920dbf910b42e5bf96d54a/src/blog/migrations/__init__.py -------------------------------------------------------------------------------- /src/blog/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.auth.models import User 3 | from django.utils import timezone 4 | 5 | # Create your models here. 6 | 7 | class Article(models.Model): 8 | title = models.CharField(max_length=200) 9 | content = models.TextField() 10 | pub_date = models.DateTimeField('date published', null=True, blank=True) 11 | is_published = models.BooleanField('ready to publish', default=False) 12 | author = models.ForeignKey(User, on_delete=models.CASCADE) 13 | 14 | def __str__(self): 15 | return self.title 16 | 17 | def save(self, *args, **kwargs): 18 | if self.is_published and not self.pub_date: 19 | self.pub_date = timezone.now() 20 | super().save(*args, **kwargs) 21 | -------------------------------------------------------------------------------- /src/blog/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /src/blog/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from . import views 3 | 4 | app_name = 'blog' 5 | 6 | urlpatterns = [ 7 | path('', views.article_list, name='article_list'), 8 | path('/', views.article_detail, name='article_detail'), 9 | path('comment//delete/', views.delete_comment, name='delete_comment'), 10 | ] -------------------------------------------------------------------------------- /src/blog/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render, get_object_or_404, redirect 2 | from django.contrib.auth.decorators import login_required 3 | from django.contrib import messages 4 | from django.contrib.contenttypes.models import ContentType 5 | from .models import Article 6 | from comments.models import Comment 7 | from .forms import CommentForm 8 | 9 | def article_list(request): 10 | articles = Article.objects.filter(is_published=True).order_by('-pub_date') 11 | context = {'articles': articles} 12 | return render(request, 'blog/article_list.html', context) 13 | 14 | def article_detail(request, pk): 15 | article = get_object_or_404(Article, pk=pk, is_published=True) 16 | comments = Comment.objects.filter( 17 | content_type=ContentType.objects.get_for_model(Article), 18 | object_id=article.id 19 | ).order_by('-created_at') 20 | 21 | if request.method == 'POST' and request.user.is_authenticated: 22 | form = CommentForm(request.POST) 23 | if form.is_valid(): 24 | new_comment = form.save(commit=False) 25 | new_comment.user = request.user 26 | new_comment.content_object = article 27 | new_comment.save() 28 | messages.success(request, 'Your comment has been added.') 29 | return redirect('blog:article_detail', pk=pk) 30 | else: 31 | form = CommentForm() 32 | 33 | context = { 34 | 'article': article, 35 | 'comments': comments, # Add this line to include comments in the context 36 | 'form': form, 37 | } 38 | return render(request, 'blog/article_detail.html', context) 39 | 40 | @login_required 41 | def delete_comment(request, comment_id): 42 | comment = get_object_or_404(Comment, id=comment_id, user=request.user) 43 | article_id = comment.content_object.id 44 | comment.delete() 45 | messages.success(request, 'Your comment has been deleted.') 46 | return redirect('blog:article_detail', pk=article_id) 47 | -------------------------------------------------------------------------------- /src/cfehome/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/Cursor-Django/62068036fd145b8473920dbf910b42e5bf96d54a/src/cfehome/__init__.py -------------------------------------------------------------------------------- /src/cfehome/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for cfehome project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/5.1/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'cfehome.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /src/cfehome/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.contrib.auth.forms import UserCreationForm 3 | from django.contrib.auth.models import User 4 | 5 | class SignUpForm(UserCreationForm): 6 | email = forms.EmailField(max_length=254, help_text='Required. Inform a valid email address.') 7 | 8 | class Meta: 9 | model = User 10 | fields = ('username', 'email', 'password1', 'password2', ) -------------------------------------------------------------------------------- /src/cfehome/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for cfehome project. 3 | 4 | Generated by 'django-admin startproject' using Django 5.1.1. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/5.1/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/5.1/ref/settings/ 11 | """ 12 | 13 | from pathlib import Path 14 | from decouple import config, Csv 15 | import os 16 | 17 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 18 | BASE_DIR = Path(__file__).resolve().parent.parent 19 | 20 | 21 | # Quick-start development settings - unsuitable for production 22 | # See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/ 23 | 24 | # SECURITY WARNING: keep the secret key used in production secret! 25 | SECRET_KEY = config('SECRET_KEY') 26 | 27 | # SECURITY WARNING: don't run with debug turned on in production! 28 | DEBUG = config('DEBUG', default=False, cast=bool) 29 | 30 | ALLOWED_HOSTS = config('ALLOWED_HOSTS', default='', cast=Csv()) 31 | 32 | 33 | # Application definition 34 | 35 | INSTALLED_APPS = [ 36 | 'django.contrib.admin', 37 | 'django.contrib.auth', 38 | 'django.contrib.contenttypes', 39 | 'django.contrib.sessions', 40 | 'django.contrib.messages', 41 | 'django.contrib.staticfiles', 42 | 'blog', # Add the new blog app here 43 | 'comments', # Add the new comments app 44 | 'contact', # Add the new contact app 45 | ] 46 | 47 | MIDDLEWARE = [ 48 | 'django.middleware.security.SecurityMiddleware', 49 | 'django.contrib.sessions.middleware.SessionMiddleware', 50 | 'django.middleware.common.CommonMiddleware', 51 | 'django.middleware.csrf.CsrfViewMiddleware', 52 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 53 | 'django.contrib.messages.middleware.MessageMiddleware', 54 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 55 | ] 56 | 57 | ROOT_URLCONF = 'cfehome.urls' 58 | 59 | TEMPLATES = [ 60 | { 61 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 62 | 'DIRS': [os.path.join(BASE_DIR, 'templates')], 63 | 'APP_DIRS': True, 64 | 'OPTIONS': { 65 | 'context_processors': [ 66 | 'django.template.context_processors.debug', 67 | 'django.template.context_processors.request', 68 | 'django.contrib.auth.context_processors.auth', 69 | 'django.contrib.messages.context_processors.messages', 70 | ], 71 | }, 72 | }, 73 | ] 74 | 75 | WSGI_APPLICATION = 'cfehome.wsgi.application' 76 | 77 | 78 | # Database 79 | # https://docs.djangoproject.com/en/5.1/ref/settings/#databases 80 | 81 | DATABASES = { 82 | 'default': { 83 | 'ENGINE': 'django.db.backends.sqlite3', 84 | 'NAME': BASE_DIR / 'db.sqlite3', 85 | } 86 | } 87 | 88 | DATABASE_URL = config('DATABASE_URL', default=None) 89 | if DATABASE_URL is not None: 90 | import dj_database_url 91 | DATABASES = { 92 | 'default': dj_database_url.config( 93 | default=DATABASE_URL, 94 | conn_max_age=600, 95 | conn_health_checks=False, 96 | ) 97 | } 98 | 99 | 100 | # Password validation 101 | # https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators 102 | 103 | AUTH_PASSWORD_VALIDATORS = [ 104 | { 105 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 106 | }, 107 | { 108 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 109 | }, 110 | { 111 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 112 | }, 113 | { 114 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 115 | }, 116 | ] 117 | 118 | 119 | # Internationalization 120 | # https://docs.djangoproject.com/en/5.1/topics/i18n/ 121 | 122 | LANGUAGE_CODE = 'en-us' 123 | 124 | TIME_ZONE = 'UTC' 125 | 126 | USE_I18N = True 127 | 128 | USE_TZ = True 129 | 130 | 131 | # Static files (CSS, JavaScript, Images) 132 | # https://docs.djangoproject.com/en/5.1/howto/static-files/ 133 | 134 | 135 | STATIC_URL = '/static/' 136 | STATIC_ROOT = BASE_DIR / "static" # /static/ 137 | STATICFILES_DIRS = [ 138 | BASE_DIR / "staticfiles", 139 | ] 140 | 141 | # Default primary key field type 142 | # https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field 143 | 144 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' 145 | -------------------------------------------------------------------------------- /src/cfehome/urls.py: -------------------------------------------------------------------------------- 1 | """ 2 | URL configuration for cfehome project. 3 | 4 | The `urlpatterns` list routes URLs to views. For more information please see: 5 | https://docs.djangoproject.com/en/5.1/topics/http/urls/ 6 | Examples: 7 | Function views 8 | 1. Add an import: from my_app import views 9 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 10 | Class-based views 11 | 1. Add an import: from other_app.views import Home 12 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 13 | Including another URLconf 14 | 1. Import the include() function: from django.urls import include, path 15 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 16 | """ 17 | from django.contrib import admin 18 | from django.urls import path, include 19 | from .views import ( 20 | home_view, about_view, pricing_view, products_view, 21 | tech_stack, product_detail_view, login_view, logout_view, signup_view 22 | ) 23 | from contact.views import contact_view 24 | 25 | urlpatterns = [ 26 | path('admin/', admin.site.urls), 27 | path('contact/', contact_view, name='contact'), 28 | path('', home_view, name='home'), 29 | path('about/', about_view, name='about'), 30 | path('pricing/', pricing_view, name='pricing'), 31 | path('products/', products_view, name='products'), 32 | path('tech-stack/', tech_stack, name='tech_stack'), 33 | path('products//', product_detail_view, name='product_detail'), 34 | path('blog/', include('blog.urls', namespace='blog')), 35 | path('login/', login_view, name='login'), 36 | path('logout/', logout_view, name='logout'), # Add this line 37 | path('signup/', signup_view, name='signup'), 38 | ] 39 | -------------------------------------------------------------------------------- /src/cfehome/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render, redirect 2 | import random 3 | from django.contrib.auth import authenticate, login, logout 4 | from django.contrib import messages 5 | 6 | from django.shortcuts import render 7 | from django.contrib.auth.decorators import login_required 8 | from django.db.models import Q 9 | from comments.models import Comment 10 | from contact.models import ContactEntry 11 | 12 | from .forms import SignUpForm 13 | 14 | def home_view(request): 15 | if request.user.is_authenticated: 16 | return user_dashboard_view(request) 17 | else: 18 | context = { 19 | 'name': 'justin mitchel', 20 | 'email': 'codingforentrepreneurs@gmail.com' 21 | } 22 | return render(request, 'home.html', context) 23 | 24 | def about_view(request): 25 | context = { 26 | 'title': 'About Us', 27 | 'description': 'We are a team of passionate developers working on the Cursor Django project.' 28 | } 29 | return render(request, 'about.html', context) 30 | 31 | # Add these new views 32 | def contact_view(request): 33 | context = { 34 | 'title': 'Contact Us', 35 | 'description': 'Get in touch with us for any inquiries.' 36 | } 37 | return render(request, 'contact.html', context) 38 | 39 | def pricing_view(request): 40 | context = { 41 | 'title': 'Pricing', 42 | 'description': 'Check out our competitive pricing plans.' 43 | } 44 | return render(request, 'pricing.html', context) 45 | 46 | def products_view(request): 47 | context = { 48 | 'title': 'Our Products', 49 | 'description': 'Explore our range of products and services.' 50 | } 51 | return render(request, 'products.html', context) 52 | 53 | def tech_stack(request): 54 | return render(request, 'tech_stack.html') 55 | 56 | 57 | def product_detail_view(request, product_id): 58 | # In a real application, you would fetch the product from a database 59 | # For this example, we'll use a dummy product 60 | product = { 61 | 'id': product_id, 62 | 'name': f'Product {product_id}', 63 | 'description': f'This is the detailed description for Product {product_id}.', 64 | 'price': round(product_id * 10 + random.uniform(0.99, 9.99), 2) 65 | } 66 | 67 | context = { 68 | 'product': product 69 | } 70 | return render(request, 'product_detail.html', context) 71 | 72 | def login_view(request): 73 | if request.method == 'POST': 74 | username = request.POST.get('username') 75 | password = request.POST.get('password') 76 | user = authenticate(request, username=username, password=password) 77 | if user is not None: 78 | login(request, user) 79 | messages.success(request, 'You have successfully logged in.') 80 | return redirect('home') 81 | else: 82 | messages.error(request, 'Invalid username or password.') 83 | return render(request, 'login.html') 84 | 85 | def logout_view(request): 86 | logout(request) 87 | messages.success(request, 'You have been successfully logged out.') 88 | return redirect('home') 89 | 90 | def signup_view(request): 91 | if request.method == 'POST': 92 | form = SignUpForm(request.POST) 93 | if form.is_valid(): 94 | user = form.save() 95 | raw_password = form.cleaned_data.get('password1') 96 | user = authenticate(username=user.username, password=raw_password) 97 | login(request, user) 98 | return redirect('home') 99 | else: 100 | form = SignUpForm() 101 | return render(request, 'signup.html', {'form': form}) 102 | 103 | 104 | 105 | @login_required 106 | def user_dashboard_view(request): 107 | user = request.user 108 | 109 | # Fetch comments and contact entries for the user 110 | comments = Comment.objects.filter(user=user) 111 | contact_entries = ContactEntry.objects.filter(user=user) 112 | 113 | # Combine querysets 114 | combined_data = list(comments) + list(contact_entries) 115 | # Sort combined data by creation date 116 | combined_data.sort(key=lambda x: x.created_at, reverse=True) 117 | 118 | # Get counts 119 | comments_count = comments.count() 120 | contact_entries_count = contact_entries.count() 121 | 122 | context = { 123 | 'user_data': combined_data, 124 | 'comments_count': comments_count, 125 | 'contact_entries_count': contact_entries_count, 126 | } 127 | 128 | return render(request, 'dashboard/user_dashboard.html', context) -------------------------------------------------------------------------------- /src/cfehome/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for cfehome project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/5.1/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'cfehome.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /src/comments/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/Cursor-Django/62068036fd145b8473920dbf910b42e5bf96d54a/src/comments/__init__.py -------------------------------------------------------------------------------- /src/comments/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /src/comments/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CommentsConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'comments' 7 | -------------------------------------------------------------------------------- /src/comments/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.1 on 2024-10-08 18:33 2 | 3 | import django.db.models.deletion 4 | from django.conf import settings 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | ('contenttypes', '0002_remove_content_type_name'), 14 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 15 | ] 16 | 17 | operations = [ 18 | migrations.CreateModel( 19 | name='Comment', 20 | fields=[ 21 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 22 | ('object_id', models.PositiveIntegerField()), 23 | ('text', models.TextField()), 24 | ('created_at', models.DateTimeField(auto_now_add=True)), 25 | ('updated_at', models.DateTimeField(auto_now=True)), 26 | ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')), 27 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 28 | ], 29 | options={ 30 | 'ordering': ['-created_at'], 31 | }, 32 | ), 33 | ] 34 | -------------------------------------------------------------------------------- /src/comments/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/Cursor-Django/62068036fd145b8473920dbf910b42e5bf96d54a/src/comments/migrations/__init__.py -------------------------------------------------------------------------------- /src/comments/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.conf import settings 3 | from django.contrib.contenttypes.fields import GenericForeignKey 4 | from django.contrib.contenttypes.models import ContentType 5 | 6 | class Comment(models.Model): 7 | user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) 8 | content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) 9 | object_id = models.PositiveIntegerField() 10 | content_object = GenericForeignKey('content_type', 'object_id') 11 | 12 | text = models.TextField() 13 | created_at = models.DateTimeField(auto_now_add=True) 14 | updated_at = models.DateTimeField(auto_now=True) 15 | 16 | class Meta: 17 | ordering = ['-created_at'] 18 | 19 | def __str__(self): 20 | return f"Comment by {self.user.username} on {self.content_object}" 21 | -------------------------------------------------------------------------------- /src/comments/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /src/comments/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /src/contact/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/Cursor-Django/62068036fd145b8473920dbf910b42e5bf96d54a/src/contact/__init__.py -------------------------------------------------------------------------------- /src/contact/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import ContactEntry 3 | 4 | @admin.register(ContactEntry) 5 | class ContactEntryAdmin(admin.ModelAdmin): 6 | list_display = ('name', 'email', 'subject', 'created_at') 7 | list_filter = ('created_at',) 8 | search_fields = ('name', 'email', 'subject', 'message') 9 | readonly_fields = ('created_at',) 10 | fieldsets = ( 11 | (None, { 12 | 'fields': ('user', 'name', 'email', 'subject', 'message') 13 | }), 14 | ('Metadata', { 15 | 'fields': ('created_at',), 16 | 'classes': ('collapse',) 17 | }), 18 | ) 19 | 20 | def get_readonly_fields(self, request, obj=None): 21 | if obj: # editing an existing object 22 | return self.readonly_fields + ('user',) 23 | return self.readonly_fields 24 | 25 | # Register your models here. 26 | -------------------------------------------------------------------------------- /src/contact/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ContactConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'contact' 7 | -------------------------------------------------------------------------------- /src/contact/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from .models import ContactEntry 3 | 4 | class ContactForm(forms.ModelForm): 5 | class Meta: 6 | model = ContactEntry 7 | fields = ['name', 'email', 'subject', 'message'] 8 | widgets = { 9 | 'name': forms.TextInput(attrs={ 10 | 'class': 'w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500', 11 | 'placeholder': 'Your Name' 12 | }), 13 | 'email': forms.EmailInput(attrs={ 14 | 'class': 'w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500', 15 | 'placeholder': 'Your Email' 16 | }), 17 | 'subject': forms.TextInput(attrs={ 18 | 'class': 'w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500', 19 | 'placeholder': 'Subject' 20 | }), 21 | 'message': forms.Textarea(attrs={ 22 | 'class': 'w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500', 23 | 'placeholder': 'Your Message', 24 | 'rows': 4 25 | }), 26 | } 27 | 28 | def __init__(self, *args, **kwargs): 29 | super().__init__(*args, **kwargs) 30 | for field in self.fields: 31 | self.fields[field].required = True -------------------------------------------------------------------------------- /src/contact/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.1 on 2024-10-08 21:58 2 | 3 | import django.db.models.deletion 4 | from django.conf import settings 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='ContactEntry', 19 | fields=[ 20 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 21 | ('name', models.CharField(max_length=255)), 22 | ('email', models.EmailField(max_length=254)), 23 | ('subject', models.CharField(max_length=255)), 24 | ('message', models.TextField()), 25 | ('created_at', models.DateTimeField(auto_now_add=True)), 26 | ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)), 27 | ], 28 | options={ 29 | 'verbose_name_plural': 'Contact Entries', 30 | }, 31 | ), 32 | ] 33 | -------------------------------------------------------------------------------- /src/contact/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/Cursor-Django/62068036fd145b8473920dbf910b42e5bf96d54a/src/contact/migrations/__init__.py -------------------------------------------------------------------------------- /src/contact/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.conf import settings 3 | 4 | # Create your models here. 5 | 6 | class ContactEntry(models.Model): 7 | user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True) 8 | name = models.CharField(max_length=255) 9 | email = models.EmailField() 10 | subject = models.CharField(max_length=255) 11 | message = models.TextField() 12 | created_at = models.DateTimeField(auto_now_add=True) 13 | 14 | def __str__(self): 15 | return f"{self.name} - {self.subject}" 16 | 17 | class Meta: 18 | verbose_name_plural = "Contact Entries" 19 | -------------------------------------------------------------------------------- /src/contact/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /src/contact/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render, redirect 2 | from django.contrib import messages 3 | from .forms import ContactForm 4 | 5 | # Create your views here. 6 | 7 | def contact_view(request): 8 | if request.method == 'POST': 9 | form = ContactForm(request.POST) 10 | if form.is_valid(): 11 | contact_entry = form.save(commit=False) 12 | if request.user.is_authenticated: 13 | contact_entry.user = request.user 14 | contact_entry.save() 15 | messages.success(request, 'Your message has been sent successfully!') 16 | return redirect('contact') # Redirect to a 'thank you' page or back to the contact form 17 | else: 18 | form = ContactForm() 19 | 20 | return render(request, 'contact/contact_form.html', {'form': form}) 21 | -------------------------------------------------------------------------------- /src/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'cfehome.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /src/templates/about.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}{{ title }} - Cursor Django{% endblock title %} 4 | 5 | {% block content %} 6 |

{{ title }}

7 |

{{ description }}

8 | {% endblock content %} -------------------------------------------------------------------------------- /src/templates/base.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | 6 | 7 | {% block title %}CFE Main Website - Home{% endblock title %} 8 | 9 | 10 | {% block extra_head %}{% endblock extra_head %} 11 | 12 | 13 | 59 | 60 | {% block content %} 61 |
62 |
63 | 64 | New CFE Website launched! See what's new 65 | 66 | 67 |

Welcome to CFE Main Website!

68 |

This is the official website for CFE, hosted on Ubuntu 24.04 LTS. We focus on delivering innovative solutions and exceptional service to our clients.

69 | 79 |
80 |
81 | 82 |
83 | 87 |
88 | {% endblock content %} 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /src/templates/blog/article_detail.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |

{{ article.title }}

6 |

7 | Published on {{ article.pub_date|date:"F d, Y" }} at {{ article.pub_date|date:"g:i A" }} by {{ article.author.username }} 8 |

9 |
10 | {{ article.content|linebreaks }} 11 |
12 | 13 |

Comments

14 | {% if user.is_authenticated %} 15 |
16 | {% csrf_token %} 17 | {{ form.as_p }} 18 | 21 |
22 | {% else %} 23 |

Please log in to leave a comment.

24 | {% endif %} 25 | 26 | {% for comment in comments %} 27 |
28 |

{{ comment.text }}

29 |

30 | By {{ comment.user.username }} on {{ comment.created_at|date:"F d, Y" }} at {{ comment.created_at|date:"g:i A" }} 31 |

32 | {% if user == comment.user %} 33 | Delete 34 | {% endif %} 35 |
36 | {% empty %} 37 |

No comments yet.

38 | {% endfor %} 39 | 40 |

Back to article list

41 |
42 | {% endblock %} -------------------------------------------------------------------------------- /src/templates/blog/article_list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |

Articles

6 |
    7 | {% for article in articles %} 8 |
  • 9 | {{ article.title }} 10 |

    11 | Published on {{ article.pub_date|date:"F d, Y" }} by {{ article.author.username }} 12 |

    13 |
  • 14 | {% empty %} 15 |
  • No articles available.
  • 16 | {% endfor %} 17 |
18 |
19 | {% endblock %} -------------------------------------------------------------------------------- /src/templates/contact.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}{{ title }} - Cursor Django{% endblock title %} 4 | 5 | {% block content %} 6 |

{{ title }}

7 |

{{ description }}

8 | {% endblock content %} -------------------------------------------------------------------------------- /src/templates/contact/contact_form.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Contact Us{% endblock title %} 4 | 5 | {% block content %} 6 |
7 |

Contact Us

8 |
9 | {% csrf_token %} 10 | {% for field in form %} 11 |
12 | 15 | {{ field }} 16 | {% if field.errors %} 17 | {% for error in field.errors %} 18 |

19 | {{ error }} 20 |

21 | {% endfor %} 22 | {% endif %} 23 |
24 | {% endfor %} 25 | 28 |
29 |
30 | {% endblock content %} -------------------------------------------------------------------------------- /src/templates/dashboard/user_dashboard.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |

Welcome to Your Dashboard, {{ request.user.username }}

6 | 7 |
8 |

Your Activity

9 |
10 |
11 |

Comments: {{ comments_count }}

12 |
13 |
14 |

Contact Entries: {{ contact_entries_count }}

15 |
16 |
17 |
18 | 19 |
20 |

Recent Activity

21 |
    22 | {% for item in user_data %} 23 |
  • 24 | {% if item.content %} 25 |

    Comment on {{ item.created_at|date:"F d, Y" }}

    26 |

    {{ item.content|truncatewords:10 }}

    27 | {% elif item.message %} 28 |

    Contact Entry on {{ item.created_at|date:"F d, Y" }}

    29 |

    {{ item.message|truncatewords:10 }}

    30 | {% endif %} 31 |
  • 32 | {% empty %} 33 |
  • No recent activity.
  • 34 | {% endfor %} 35 |
36 |
37 |
38 | {% endblock %} 39 | -------------------------------------------------------------------------------- /src/templates/home.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Welcome to Cursor Django{% endblock title %} 4 | 5 | {% block content %} 6 | {% if user.is_authenticated %} 7 |

Welcome to Cursor Django, {{ user.username }}!

8 |

This is the new homepage for your Django project.

9 | {% else %} 10 |

Welcome to Cursor Django

11 |

This is the new homepage for your Django project.

12 |

Created by: {{ name }}

13 |

Contact: {{ email }}

14 | {% endif %} 15 | {% endblock content %} -------------------------------------------------------------------------------- /src/templates/login.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |
6 |
7 |

Login

8 | {% if messages %} 9 | {% for message in messages %} 10 |
{{ message }}
11 | {% endfor %} 12 | {% endif %} 13 |
14 | {% csrf_token %} 15 |
16 | 17 | 18 |
19 |
20 | 21 | 22 |
23 | 24 |
25 |
26 |
27 |
28 | {% endblock %} -------------------------------------------------------------------------------- /src/templates/pricing.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}{{ title }} - Cursor Django{% endblock title %} 4 | 5 | {% block content %} 6 |

{{ title }}

7 |

{{ description }}

8 | {% endblock content %} -------------------------------------------------------------------------------- /src/templates/product_detail.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |

{{ product.name }}

5 |

Product ID: {{ product.id }}

6 |

Description: {{ product.description }}

7 |

Price: ${{ product.price|floatformat:2 }}

8 | {% endblock %} -------------------------------------------------------------------------------- /src/templates/products.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}{{ title }} - Cursor Django{% endblock title %} 4 | 5 | {% block content %} 6 |

{{ title }}

7 |

{{ description }}

8 | {% endblock content %} -------------------------------------------------------------------------------- /src/templates/signup.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |

Sign up

5 |
6 | {% csrf_token %} 7 | {{ form.as_p }} 8 | 9 |
10 | {% endblock %} -------------------------------------------------------------------------------- /src/templates/tech_stack.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |

Our Tech Stack

5 |
    6 |
  • Django: Web framework
  • 7 |
  • Python 3.12: Programming language
  • 8 |
  • Gunicorn: WSGI HTTP Server
  • 9 |
  • Ubuntu 24.04 LTS: Operating System
  • 10 |
  • python-decouple: Environment variables manager
  • 11 |
  • GitHub: Version control and repository hosting
  • 12 |
13 | {% endblock %} --------------------------------------------------------------------------------