diff --git a/polls/__init__.py b/apps/polls/__init__.py similarity index 100% rename from polls/__init__.py rename to apps/polls/__init__.py diff --git a/polls/admin.py b/apps/polls/admin.py similarity index 51% rename from polls/admin.py rename to apps/polls/admin.py index 8c38f3f..4cfee41 100644 --- a/polls/admin.py +++ b/apps/polls/admin.py @@ -1,3 +1,6 @@ from django.contrib import admin +from .models import Question + # Register your models here. +admin.site.register(Question) diff --git a/polls/apps.py b/apps/polls/apps.py similarity index 73% rename from polls/apps.py rename to apps/polls/apps.py index d0f109e..afeb961 100644 --- a/polls/apps.py +++ b/apps/polls/apps.py @@ -2,4 +2,4 @@ from django.apps import AppConfig class PollsConfig(AppConfig): - name = 'polls' + name = "apps.polls" diff --git a/apps/polls/migrations/0001_initial.py b/apps/polls/migrations/0001_initial.py new file mode 100644 index 0000000..bc3d297 --- /dev/null +++ b/apps/polls/migrations/0001_initial.py @@ -0,0 +1,32 @@ +# Generated by Django 6.0 on 2025-12-21 22:37 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Question', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('question_text', models.CharField(max_length=200)), + ('pub_date', models.DateTimeField(verbose_name='date published')), + ], + ), + migrations.CreateModel( + name='Choice', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('choice_text', models.CharField(max_length=200)), + ('votes', models.IntegerField(default=0)), + ('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='polls.question')), + ], + ), + ] diff --git a/polls/migrations/__init__.py b/apps/polls/migrations/__init__.py similarity index 100% rename from polls/migrations/__init__.py rename to apps/polls/migrations/__init__.py diff --git a/apps/polls/models.py b/apps/polls/models.py new file mode 100644 index 0000000..d21b59b --- /dev/null +++ b/apps/polls/models.py @@ -0,0 +1,26 @@ +import datetime + +from django.db import models +from django.utils import timezone + +# Create your models here. + + +class Question(models.Model): + question_text = models.CharField(max_length=200) + pub_date = models.DateTimeField("date published") + + def __str__(self): + return self.question_text + + def was_published_recently(self): + return self.pub_date >= timezone.now() - datetime.timedelta(days=1) + + +class Choice(models.Model): + question = models.ForeignKey(Question, on_delete=models.CASCADE) + choice_text = models.CharField(max_length=200) + votes = models.IntegerField(default=0) + + def __str__(self): + return self.choice_text diff --git a/apps/polls/templates/polls/detail.html b/apps/polls/templates/polls/detail.html new file mode 100644 index 0000000..f0d89b2 --- /dev/null +++ b/apps/polls/templates/polls/detail.html @@ -0,0 +1,14 @@ +
+{% csrf_token %} +
+

{{ question.question_text }}

+ {% if error_message %} +

{{ error_message }}

+ {% endif %} + {% for choice in question.choice_set.all %} + +
+ {% endfor %} +
+ +
diff --git a/apps/polls/templates/polls/index.html b/apps/polls/templates/polls/index.html new file mode 100644 index 0000000..cc21d76 --- /dev/null +++ b/apps/polls/templates/polls/index.html @@ -0,0 +1,13 @@ +{% if latest_question_list %} + +{% else %} +

No polls are available.

+{% endif %} diff --git a/apps/polls/templates/polls/results.html b/apps/polls/templates/polls/results.html new file mode 100644 index 0000000..3b2c74f --- /dev/null +++ b/apps/polls/templates/polls/results.html @@ -0,0 +1,9 @@ +

{{ question.question_text }}

+ + + +Vote again? diff --git a/polls/tests.py b/apps/polls/tests.py similarity index 100% rename from polls/tests.py rename to apps/polls/tests.py diff --git a/apps/polls/urls.py b/apps/polls/urls.py new file mode 100644 index 0000000..8e7e15e --- /dev/null +++ b/apps/polls/urls.py @@ -0,0 +1,15 @@ +from django.urls import path + +from . import views + +app_name = "polls" +urlpatterns = [ + # ex: /polls/ + path("", views.index, name="index"), + # ex: /polls/5/ + path("specifics//", views.detail, name="detail"), + # ex: /polls/5/results/ + path("/results/", views.results, name="results"), + # ex: /polls/5/vote/ + path("/vote/", views.vote, name="vote"), +] diff --git a/apps/polls/views.py b/apps/polls/views.py new file mode 100644 index 0000000..b2eba60 --- /dev/null +++ b/apps/polls/views.py @@ -0,0 +1,45 @@ +from django.db.models import F +from django.http import HttpResponse, HttpResponseRedirect +from django.shortcuts import get_object_or_404, render +from django.urls import reverse + +from .models import Choice, Question + + +def index(request): + latest_question_list = Question.objects.order_by("-pub_date")[:5] + context = {"latest_question_list": latest_question_list} + return render(request, "polls/index.html", context) + + +def detail(request, question_id): + question = get_object_or_404(Question, pk=question_id) + return render(request, "polls/detail.html", {"question": question}) + + +def results(request, question_id): + question = get_object_or_404(Question, pk=question_id) + return render(request, "polls/results.html", {"question": question}) + + +def vote(request, question_id): + question = get_object_or_404(Question, pk=question_id) + try: + selected_choice = question.choice_set.get(pk=request.POST["choice"]) + except (KeyError, Choice.DoesNotExist): + # Redisplay the question voting form. + return render( + request, + "polls/detail.html", + { + "question": question, + "error_message": "You didn't select a choice.", + }, + ) + else: + selected_choice.votes = F("votes") + 1 + selected_choice.save() + # Always return an HttpResponseRedirect after successfully dealing + # with POST data. This prevents data from being posted twice if a + # user hits the Back button. + return HttpResponseRedirect(reverse("polls:results", args=(question.id,))) diff --git a/django_movies/settings.py b/django_movies/settings.py index a7fe82d..71ee1cc 100644 --- a/django_movies/settings.py +++ b/django_movies/settings.py @@ -20,7 +20,7 @@ BASE_DIR = Path(__file__).resolve().parent.parent # See https://docs.djangoproject.com/en/6.0/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = 'django-insecure-yt9sy+oy77u7i3adf2&jno_&%448n5ic&ih9tk#s9ad3gxt$#5' +SECRET_KEY = "django-insecure-yt9sy+oy77u7i3adf2&jno_&%448n5ic&ih9tk#s9ad3gxt$#5" # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True @@ -31,51 +31,52 @@ ALLOWED_HOSTS = [] # Application definition INSTALLED_APPS = [ - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', + "apps.polls.apps.PollsConfig", + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", ] MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", ] -ROOT_URLCONF = 'django_movies.urls' +ROOT_URLCONF = "django_movies.urls" TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", ], }, }, ] -WSGI_APPLICATION = 'django_movies.wsgi.application' +WSGI_APPLICATION = "django_movies.wsgi.application" # Database # https://docs.djangoproject.com/en/6.0/ref/settings/#databases DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': BASE_DIR / 'db.sqlite3', + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": BASE_DIR / "db.sqlite3", } } @@ -85,16 +86,16 @@ DATABASES = { AUTH_PASSWORD_VALIDATORS = [ { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", }, ] @@ -102,9 +103,9 @@ AUTH_PASSWORD_VALIDATORS = [ # Internationalization # https://docs.djangoproject.com/en/6.0/topics/i18n/ -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = "en-us" -TIME_ZONE = 'UTC' +TIME_ZONE = "America/New_York" USE_I18N = True @@ -114,4 +115,4 @@ USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/6.0/howto/static-files/ -STATIC_URL = 'static/' +STATIC_URL = "static/" diff --git a/django_movies/urls.py b/django_movies/urls.py index 458b564..1561e07 100644 --- a/django_movies/urls.py +++ b/django_movies/urls.py @@ -20,5 +20,5 @@ from django.urls import include, path urlpatterns = [ path("admin/", admin.site.urls), - path("polls/", include("polls.urls")), + path("polls/", include("apps.polls.urls")), ] diff --git a/polls/models.py b/polls/models.py deleted file mode 100644 index 71a8362..0000000 --- a/polls/models.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.db import models - -# Create your models here. diff --git a/polls/urls.py b/polls/urls.py deleted file mode 100644 index 5119061..0000000 --- a/polls/urls.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.urls import path - -from . import views - -urlpatterns = [ - path("", views.index, name="index"), -] diff --git a/polls/views.py b/polls/views.py deleted file mode 100644 index d8959da..0000000 --- a/polls/views.py +++ /dev/null @@ -1,9 +0,0 @@ -from django.http import HttpResponse - - -def index(request): - return HttpResponse("Hello, world. You're at the polls index.") - - -def index2(request): - return HttpResponse("Hello, world. You're at the polls index #2.")