├── .gitignore
├── .travis.yml
├── Procfile
├── README.md
├── __init__.py
├── constants.py
├── developer
├── __init__.py
├── admin.py
├── apps.py
├── forms.py
├── migrations
│ ├── 0001_initial.py
│ └── __init__.py
├── models.py
├── templates
│ ├── developer_create.html
│ ├── developer_list.html
│ └── games_by_developer.html
├── tests.py
├── urls.py
└── views.py
├── examples
├── example1.png
└── example2.png
├── final_project_coderslab
├── __init__.py
├── celery.py
├── settings.py
├── storage_backends.py
├── urls.py
└── wsgi.py
├── game
├── __init__.py
├── admin.py
├── apps.py
├── forms.py
├── migrations
│ ├── 0001_initial.py
│ └── __init__.py
├── models.py
├── templates
│ ├── game_create.html
│ ├── game_details.html
│ └── game_list.html
├── tests.py
├── urls.py
└── views.py
├── game_recommendation
├── __init__.py
├── admin.py
├── apps.py
├── forms.py
├── migrations
│ └── __init__.py
├── models.py
├── templates
│ ├── recommend_by_rated_games.html
│ ├── recommend_by_rated_games_result.html
│ ├── recommend_games_manually.html
│ └── recommend_games_manually_result.html
├── tests.py
├── urls.py
└── views.py
├── genre
├── __init__.py
├── admin.py
├── apps.py
├── forms.py
├── migrations
│ ├── 0001_initial.py
│ └── __init__.py
├── models.py
├── templates
│ ├── games_by_genre.html
│ ├── genre_create.html
│ └── genre_list.html
├── tests.py
├── urls.py
└── views.py
├── locale
└── pl
│ └── LC_MESSAGES
│ └── django.po
├── main_app
├── __init__.py
├── admin.py
├── apps.py
├── context_processor.py
├── migrations
│ └── __init__.py
├── models.py
├── tasks.py
├── templates
│ ├── 403.html
│ ├── about.html
│ └── download_games_to_csv_result.html
├── tests.py
├── urls.py
└── views.py
├── manage.py
├── media
└── csv
│ ├── game_data.csv
│ └── gaming_quotes.csv
├── news_api
├── __init__.py
├── admin.py
├── apps.py
├── migrations
│ └── __init__.py
├── models.py
├── templates
│ └── news_main.html
├── tests.py
├── urls.py
└── views.py
├── requirements.txt
├── rest_api
├── __init__.py
├── admin.py
├── apps.py
├── migrations
│ └── __init__.py
├── models.py
├── serializers.py
├── templates
│ └── rest_api_main.html
├── tests.py
├── urls.py
└── views.py
├── runtime.txt
├── settings.ini.example
├── setup.cfg
├── static
├── css
│ ├── hover-min.css
│ └── style.css
├── img
│ ├── IGN.jpg
│ ├── icon.jpg
│ ├── icon.png
│ ├── ign.png
│ ├── logo.jpg
│ ├── new_banner.jpg
│ ├── polygon.png
│ ├── techradar.png
│ └── verge.png
└── js
│ ├── dropdown_hover.js
│ ├── jquery-3.3.1.min.js
│ ├── main.js
│ └── slider.js
├── tag
├── __init__.py
├── admin.py
├── apps.py
├── forms.py
├── migrations
│ ├── 0001_initial.py
│ └── __init__.py
├── models.py
├── templates
│ ├── games_by_tag.html
│ ├── tag_create.html
│ └── tag_list.html
├── tests.py
├── urls.py
└── views.py
├── templates
├── base.html
├── message_block.html
├── navbar_staff.html
├── navbar_user_authenticated.html
└── navbar_user_unauthenticated.html
├── users
├── __init__.py
├── admin.py
├── apps.py
├── forms.py
├── migrations
│ ├── 0001_initial.py
│ └── __init__.py
├── models.py
├── templates
│ ├── user_create.html
│ ├── user_list.html
│ └── user_login.html
├── tests.py
├── urls.py
└── views.py
└── utils
└── tests.py
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### Python template
3 | # Byte-compiled / optimized / DLL files
4 | __pycache__/
5 | *.py[cod]
6 | *$py.class
7 |
8 | # C extensions
9 | ../venv/lib/python3.5/lib-dynload/_testbuffer.cpython-35m-x86_64-linux-gnu.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 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 | MANIFEST
29 |
30 | # PyInstaller
31 | # Usually these files are written by a python script from a template
32 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
33 | *.manifest
34 | *.spec
35 |
36 | # Installer logs
37 | pip-log.txt
38 | pip-delete-this-directory.txt
39 |
40 | # Unit test / coverage reports
41 | htmlcov/
42 | .tox/
43 | .coverage
44 | .coverage.*
45 | .cache
46 | nosetests.xml
47 | coverage.xml
48 | *.cover
49 | .hypothesis/
50 | .pytest_cache/
51 |
52 | # Translations
53 | *.mo
54 | *.pot
55 |
56 | # Django stuff:
57 | *.log
58 | local_settings.py
59 | db.sqlite3
60 |
61 | # Flask stuff:
62 | instance/
63 | .webassets-cache
64 |
65 | # Scrapy stuff:
66 | .scrapy
67 |
68 | # Sphinx documentation
69 | docs/_build/
70 |
71 | # PyBuilder
72 | target/
73 |
74 | # Jupyter Notebook
75 | .ipynb_checkpoints
76 |
77 | # pyenv
78 | .python-version
79 |
80 | # celery beat schedule file
81 | celerybeat-schedule
82 |
83 | # SageMath parsed files
84 | *.sage.py
85 |
86 | # Environments
87 | .env
88 | .venv
89 | env/
90 | venv/
91 | ENV/
92 | env.bak/
93 | venv.bak/
94 |
95 | # Spyder project settings
96 | .spyderproject
97 | .spyproject
98 |
99 | # Rope project settings
100 | .ropeproject
101 |
102 | # mkdocs documentation
103 | /site
104 |
105 | # mypy
106 | .mypy_cache/
107 | ### JetBrains template
108 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
109 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
110 |
111 | # User-specific stuff
112 | .idea/**/workspace.xml
113 | .idea/**/tasks.xml
114 | .idea/**/usage.statistics.xml
115 | .idea/**/dictionaries
116 | .idea/**/shelf
117 |
118 | # Sensitive or high-churn files
119 | .idea/**/dataSources/
120 | .idea/**/dataSources.ids
121 | .idea/**/dataSources.local.xml
122 | .idea/**/sqlDataSources.xml
123 | .idea/**/dynamic.xml
124 | .idea/**/uiDesigner.xml
125 | .idea/**/dbnavigator.xml
126 |
127 | # Gradle
128 | .idea/**/gradle.xml
129 | .idea/**/libraries
130 |
131 | # Gradle and Maven with auto-import
132 | # When using Gradle or Maven with auto-import, you should exclude module files,
133 | # since they will be recreated, and may cause churn. Uncomment if using
134 | # auto-import.
135 | # .idea/modules.xml
136 | # .idea/*.iml
137 | # .idea/modules
138 |
139 | # CMake
140 | cmake-build-*/
141 |
142 | # Mongo Explorer plugin
143 | .idea/**/mongoSettings.xml
144 |
145 | # File-based project format
146 | *.iws
147 |
148 | # IntelliJ
149 | out/
150 |
151 | # mpeltonen/sbt-idea plugin
152 | .idea_modules/
153 |
154 | # JIRA plugin
155 | atlassian-ide-plugin.xml
156 |
157 | # Cursive Clojure plugin
158 | .idea/replstate.xml
159 |
160 | # Crashlytics plugin (for Android Studio and IntelliJ)
161 | com_crashlytics_export_strings.xml
162 | crashlytics.properties
163 | crashlytics-build.properties
164 | fabric.properties
165 |
166 | # Editor-based Rest Client
167 | .idea/httpRequests
168 |
169 | settings.ini
170 | media/images
171 | staticfiles
172 | .idea
173 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | dist: xenial # required for Python >= 3.7
2 | language: python
3 | python:
4 | - "3.7"
5 |
6 | services: postgresql
7 |
8 | env:
9 | - DJANGO=2.1.2
10 |
11 | before_install:
12 | - export DJANGO_SETTINGS_MODULE=final_project_coderslab.settings
13 | - export PYTHONPATH=$HOME/builds/dawidbudzynski/gameweb_python_django
14 | - export PIP_USE_MIRRORS=true
15 |
16 | install:
17 | - pip install -r requirements.txt
18 | - pip install django==$DJANGO --quiet
19 | - pip install psycopg2 --quiet
20 |
21 | before_script:
22 | - psql -c "CREATE DATABASE travis_db;" -U postgres
23 |
24 | script:
25 | - python manage.py makemigrations
26 | - python manage.py migrate
27 | - python manage.py collectstatic --noinput
28 | - coverage run --source='.' manage.py test
29 |
30 | after_success:
31 | coverage report
32 |
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: gunicorn final_project_coderslab.wsgi
2 | worker: celery -A final_project_coderslab worker
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Gameweb [](https://travis-ci.org/dawidbudzynski/gameweb_python_django)
2 |
3 |
4 |
5 | ## General info
6 | A web application made using Python 3, Django 2, Bootstrap and REST API.
7 | It's website about technology where user can find interesting news (displayed used API),
8 | add new games to database and find new titles (using recommendation feature).
9 |
10 | ## Main functions
11 | * displaying gaming and tech news - updated every few hours using API
12 | * adding new users and games to database
13 | * recommending new games based on user's preferences
14 |
15 | ## Technologies
16 | * Python 3.7
17 | * Django 2.1.2
18 | * Bootstrap 4
19 | * Django REST Framework
20 | * Celery
21 | * Redis
22 | * AWS S3
23 | * Travis CI
24 |
25 | ## Setup
26 | To run this project:
27 | 1. Create PostgreSQL database
28 | 2. Rename settings.ini.example to settings.ini and fill required fields.
29 | 3. Install required libraries using pip:
30 | ```
31 | $ pip install -r requirements.txt
32 | ```
33 | 4. Apply migrations::
34 | ```
35 | $ python manage.py migrate
36 | ```
37 | 5. To run your local server use command:
38 | ```
39 | $ python manage.py runserver
40 | ```
41 |
42 | To run asynchronous tasks:
43 | 1. Install Redis
44 | 2. Start Redis server:
45 | ```
46 | $ redis-server
47 | ```
48 | 3. Open new terminal tab
49 | 4. Go to project root
50 | 5. Start Celery worker:
51 | ```
52 | $ celery -A final_project_coderslab worker -l info
53 | ```
54 |
55 | To run periodic tasks:
56 | 1. Open new terminal tab
57 | 2. Go to project root
58 | 3. Start Celery beat:
59 | ```
60 | $ celery -A final_project_coderslab beat -l info
61 | ```
62 |
63 | ## Demo
64 | ## http://dawidb.pythonanywhere.com
65 |
66 | **Gaming and tech news updated every few hours using API from sites like: Polygon, IGN, The Verge and TechRadar**
67 |
68 | 
69 |
70 |
71 | **Games recommendation based on user's preferences**
72 |
73 | 
74 |
75 |
--------------------------------------------------------------------------------
/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dawidbudzynski/gameweb_python_django/b95719f04b43d2cd0c804e5972a43648e05d9e39/__init__.py
--------------------------------------------------------------------------------
/constants.py:
--------------------------------------------------------------------------------
1 | YEARS = {(x, x) for x in range(1998, 2020)}
2 | SORTED_YEARS = sorted(YEARS, key=lambda x: x[1], reverse=True)
3 |
4 | RATING = {(i, '*' * i) for i in range(1, 11)}
5 | SORTED_RATING = sorted(RATING, key=lambda x: x[0], reverse=True)
6 |
7 | NEWS_SOURCE_DATA_ALL = {
8 | 'ign': {
9 | 'api_name': 'IGN',
10 | 'image_url': 'img/ign.png'
11 | },
12 | 'polygon': {
13 | 'api_name': 'Polygon',
14 | 'image_url': 'img/polygon.png'
15 | },
16 | 'techradar': {
17 | 'api_name': 'TechRadar',
18 | 'image_url': 'img/techradar.png'
19 | },
20 | 'the-verge': {
21 | 'api_name': 'The Verge',
22 | 'image_url': 'img/verge.png'
23 | },
24 | }
25 |
--------------------------------------------------------------------------------
/developer/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dawidbudzynski/gameweb_python_django/b95719f04b43d2cd0c804e5972a43648e05d9e39/developer/__init__.py
--------------------------------------------------------------------------------
/developer/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 |
3 | from .models import Developer
4 |
5 | admin.site.register(Developer)
6 |
--------------------------------------------------------------------------------
/developer/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class DeveloperConfig(AppConfig):
5 | name = 'developer'
6 |
--------------------------------------------------------------------------------
/developer/forms.py:
--------------------------------------------------------------------------------
1 | from django.forms import (CharField, Form)
2 | from django.utils.translation import ugettext_lazy as _
3 |
4 |
5 | class AddDeveloperForm(Form):
6 | name = CharField(max_length=64, label=_('Name'))
7 |
--------------------------------------------------------------------------------
/developer/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.1.2 on 2019-05-18 10:16
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | initial = True
9 |
10 | dependencies = [
11 | ]
12 |
13 | operations = [
14 | migrations.CreateModel(
15 | name='Developer',
16 | fields=[
17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18 | ('name', models.CharField(max_length=64)),
19 | ],
20 | ),
21 | ]
22 |
--------------------------------------------------------------------------------
/developer/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dawidbudzynski/gameweb_python_django/b95719f04b43d2cd0c804e5972a43648e05d9e39/developer/migrations/__init__.py
--------------------------------------------------------------------------------
/developer/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
3 |
4 | class Developer(models.Model):
5 | name = models.CharField(max_length=64)
6 |
7 | def __str__(self):
8 | return self.name
9 |
--------------------------------------------------------------------------------
/developer/templates/developer_create.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load bootstrap4 i18n staticfiles %}
3 | {% block content %}
4 |
5 |
6 |
7 | {% trans 'Create developer' %} |
8 |
9 |
10 |
11 |
12 | {% include 'message_block.html' %}
13 |
14 |
15 |
20 | |
21 |
22 |
23 | {% endblock %}
24 |
--------------------------------------------------------------------------------
/developer/templates/developer_list.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load bootstrap4 i18n staticfiles %}
3 |
4 | {% block content %}
5 |
6 |
7 |
8 | {% trans 'Developers' %} |
9 |
10 |
11 |
12 | {% if user.is_authenticated %}
13 |
14 |
15 |
16 | {% trans 'Create developer' %}
18 | |
19 |
20 |
21 | {% endif %}
22 |
23 | {% include 'message_block.html' %}
24 | {% for developer in object_list %}
25 |
26 |
27 |
28 |
29 |
44 | |
45 |
46 |
47 |
48 | {% endfor %}
49 | {% endblock %}
50 |
--------------------------------------------------------------------------------
/developer/templates/games_by_developer.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load bootstrap4 i18n staticfiles %}
3 | {% block content %}
4 |
5 |
6 |
7 | {% trans 'Games made by: ' %}{{ selected_developer.name }} |
8 |
9 |
10 |
11 | {% for game in all_games_with_developer %}
12 |
13 |
14 |
15 | {{ game.title }} |
16 |
17 |
18 |
19 |
20 | {% trans 'Genre' %} |
21 |
22 | {% for genre in game.genre.all %}
23 |
26 | {% endfor %}
27 | |
28 |
29 |
30 | |
31 |
32 |
33 | {% trans 'Developer' %} |
34 | |
36 |
37 |
38 | {% trans 'Tags' %} |
39 |
40 | {% for tag in game.tags.all %}
41 | {{ tag.name }}
42 | {% endfor %}
43 | |
44 |
45 |
46 | {% trans 'Year' %} |
47 | {{ game.year }} |
48 |
49 |
50 |
51 | {% trans 'Delete' %}
53 | |
54 |
55 |
56 |
57 | {% endfor %}
58 | {% endblock %}
59 |
--------------------------------------------------------------------------------
/developer/tests.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth.models import User
2 | from django.test import TestCase
3 | from django.urls import reverse
4 |
5 | from developer.models import Developer
6 | from .forms import AddDeveloperForm
7 |
8 |
9 | class DeveloperFormTests(TestCase):
10 |
11 | def test_developer_form(self):
12 | # redirects to login screen
13 | response = self.client.get(reverse('developer:developer-create'))
14 | self.assertEqual('/users/login?next=/pl/developer/developer_create/', response['location'])
15 | self.assertEquals(response.status_code, 302)
16 |
17 | self.client.force_login(User.objects.get_or_create(username='user_1')[0])
18 | response = self.client.get(reverse('developer:developer-create'))
19 | self.assertEquals(response.status_code, 200)
20 |
21 | form = AddDeveloperForm()
22 | self.assertFalse(form.is_valid())
23 |
24 | data = {"name": ""}
25 | form = AddDeveloperForm(data=data)
26 | self.assertFalse(form.is_valid())
27 | self.assertEqual(form.errors, {'name': ['To pole jest wymagane.']})
28 |
29 | data = {"name": "dev_1"}
30 | form = AddDeveloperForm(data=data)
31 | self.assertTrue(form.is_valid())
32 |
33 |
34 | class DeveloperListTests(TestCase):
35 |
36 | def setUp(self):
37 | Developer.objects.create(name='developer_1')
38 | Developer.objects.create(name='developer_2')
39 |
40 | def test_text_content(self):
41 | dev_object = Developer.objects.get(name='developer_1')
42 | expected_object_name = f'{dev_object.name}'
43 | self.assertEquals(expected_object_name, 'developer_1')
44 |
45 | def test_developer_list_view(self):
46 | response = self.client.get(reverse('developer:developer-list'))
47 | self.assertEqual(response.status_code, 200)
48 | self.assertContains(response, 'developer_1')
49 | self.assertEqual(len(response.context[0]['object_list']), 2)
50 | self.assertTemplateUsed(response, 'developer_list.html')
51 |
--------------------------------------------------------------------------------
/developer/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import path
2 |
3 | from . import views
4 |
5 | app_name = 'developer'
6 |
7 | urlpatterns = [
8 | path('developer_create/', views.DeveloperCreateView.as_view(),
9 | name='developer-create'),
10 | path('developer_list/', views.DeveloperListView.as_view(),
11 | name='developer-list'),
12 | path('developer_delete/', views.DeveloperDeleteView.as_view(),
13 | name='developer-delete'),
14 | path('games_by_developer/', views.GamesByDeveloperView.as_view(),
15 | name='games-by-developer')
16 | ]
17 |
--------------------------------------------------------------------------------
/developer/views.py:
--------------------------------------------------------------------------------
1 | from decouple import config
2 | from django.contrib import messages
3 | from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
4 | from django.http import HttpResponseRedirect
5 | from django.shortcuts import render
6 | from django.urls import reverse
7 | from django.utils.translation import ugettext as _
8 | from django.views import View
9 | from django.views.generic.list import ListView
10 | from game.models import Game
11 |
12 | from .forms import AddDeveloperForm
13 | from .models import Developer
14 |
15 | NEWS_API_KEY = config('NEWS_API_KEY', cast=str)
16 |
17 |
18 | class DeveloperListView(ListView):
19 | model = Developer
20 | template_name = 'developer_list.html'
21 |
22 |
23 | class DeveloperCreateView(LoginRequiredMixin, View):
24 | def get(self, request):
25 | return render(
26 | request,
27 | template_name='developer_create.html',
28 | context={'form': AddDeveloperForm().as_p()}
29 | )
30 |
31 | def post(self, request):
32 | form = AddDeveloperForm(request.POST)
33 | if form.is_valid():
34 | name = form.cleaned_data['name']
35 |
36 | if Developer.objects.filter(name=name).exists():
37 | messages.add_message(request, messages.WARNING, _('Developer with this name already exists'))
38 | return HttpResponseRedirect(reverse('developer:developer-create'))
39 |
40 | Developer.objects.create(name=name)
41 | messages.add_message(request, messages.INFO, _('Developer: {} created successfully').format(name))
42 | return HttpResponseRedirect(reverse('developer:developer-list'))
43 |
44 | messages.add_message(request, messages.ERROR, _('Form invalid'))
45 | return HttpResponseRedirect(reverse('developer:developer-create'))
46 |
47 |
48 | class DeveloperDeleteView(PermissionRequiredMixin, View):
49 | permission_required = 'developer.delete_developer'
50 | raise_exception = True
51 |
52 | def get(self, request, developer_id):
53 | developer = Developer.objects.get(id=developer_id)
54 | developer.delete()
55 | messages.add_message(request, messages.WARNING, _('Developer: {} has been deleted').format(developer.name))
56 | return HttpResponseRedirect(reverse('developer:developer-list'))
57 |
58 |
59 | class GamesByDeveloperView(View):
60 | """Display all games with selected developer"""
61 |
62 | def get(self, request, developer_id):
63 | selected_developer = Developer.objects.get(id=developer_id)
64 | ctx = {
65 | 'all_games_with_developer': Game.objects.filter(developer=selected_developer),
66 | 'selected_developer': selected_developer
67 | }
68 |
69 | return render(
70 | request,
71 | template_name='games_by_developer.html',
72 | context=ctx
73 | )
74 |
--------------------------------------------------------------------------------
/examples/example1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dawidbudzynski/gameweb_python_django/b95719f04b43d2cd0c804e5972a43648e05d9e39/examples/example1.png
--------------------------------------------------------------------------------
/examples/example2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dawidbudzynski/gameweb_python_django/b95719f04b43d2cd0c804e5972a43648e05d9e39/examples/example2.png
--------------------------------------------------------------------------------
/final_project_coderslab/__init__.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | # This will make sure the app is always imported when
4 | # Django starts so that shared_task will use this app.
5 | from .celery import app as celery_app
6 |
--------------------------------------------------------------------------------
/final_project_coderslab/celery.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | import os
4 |
5 | from celery import Celery
6 | from django.conf import settings
7 |
8 | # set the default Django settings module for the 'celery' program.
9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'final_project_coderslab.settings')
10 | app = Celery('final_project_coderslab')
11 |
12 | # Using a string here means the worker will not have to
13 | # pickle the object when using Windows.
14 | app.config_from_object('django.conf:settings')
15 | app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
16 |
17 |
18 | @app.task(bind=True)
19 | def debug_task(self):
20 | print('Request: {0!r}'.format(self.request))
21 |
--------------------------------------------------------------------------------
/final_project_coderslab/settings.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | import django_heroku
4 | from decouple import config
5 | from django.utils.translation import ugettext_lazy as _
6 |
7 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
8 | SECRET_KEY = config('SECRET_KEY', default=False, cast=str)
9 | DEBUG = config('DEBUG', default=False, cast=bool)
10 |
11 | ALLOWED_HOSTS = ['gamewebdjango.herokuapp.com']
12 |
13 | INSTALLED_APPS = [
14 | 'django.contrib.admin',
15 | 'django.contrib.auth',
16 | 'django.contrib.contenttypes',
17 | 'django.contrib.sessions',
18 | 'django.contrib.messages',
19 | 'django.contrib.staticfiles',
20 |
21 | 'bootstrap4',
22 | 'debug_toolbar',
23 | 'rest_framework',
24 | 'storages',
25 |
26 | 'developer.apps.DeveloperConfig',
27 | 'game_recommendation.apps.GameRecommendationConfig',
28 | 'game.apps.GameConfig',
29 | 'genre.apps.GenreConfig',
30 | 'main_app.apps.MainAppConfig',
31 | 'news_api.apps.NewsApiConfig',
32 | 'tag.apps.TagConfig',
33 | 'users.apps.UsersConfig',
34 | 'rest_api.apps.RestApiConfig'
35 | ]
36 |
37 | MIDDLEWARE = [
38 | 'whitenoise.middleware.WhiteNoiseMiddleware',
39 | 'django.middleware.security.SecurityMiddleware',
40 | 'django.contrib.sessions.middleware.SessionMiddleware',
41 | 'django.middleware.locale.LocaleMiddleware',
42 | 'django.middleware.common.CommonMiddleware',
43 | 'django.middleware.csrf.CsrfViewMiddleware',
44 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
45 | 'django.contrib.messages.middleware.MessageMiddleware',
46 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
47 | 'debug_toolbar.middleware.DebugToolbarMiddleware',
48 | ]
49 |
50 | ROOT_URLCONF = 'final_project_coderslab.urls'
51 |
52 | TEMPLATES = [
53 | {
54 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
55 | 'DIRS': [os.path.join(BASE_DIR, 'templates')],
56 | 'APP_DIRS': True,
57 | 'OPTIONS': {
58 | 'context_processors': [
59 | 'django.template.context_processors.debug',
60 | 'django.template.context_processors.request',
61 | 'django.contrib.auth.context_processors.auth',
62 | 'django.contrib.messages.context_processors.messages',
63 | 'django.template.context_processors.i18n',
64 | 'main_app.context_processor.gaming_quotes_processor'
65 | ],
66 | },
67 | },
68 | ]
69 |
70 | WSGI_APPLICATION = 'final_project_coderslab.wsgi.application'
71 |
72 | # DATABASE SETTINGS
73 | TRAVIS_ENVIRONMENT = 'TRAVIS' in os.environ
74 | WORK_ON_POSTGRE_SQL = config('WORK_ON_POSTGRE_SQL', default=False, cast=str)
75 | if TRAVIS_ENVIRONMENT:
76 | DATABASES = {
77 | 'default': {
78 | 'ENGINE': 'django.db.backends.postgresql_psycopg2',
79 | 'NAME': 'travis_db',
80 | 'USER': 'postgres',
81 | 'PASSWORD': '',
82 | 'HOST': 'localhost',
83 | 'PORT': '',
84 | }
85 | }
86 | elif WORK_ON_POSTGRE_SQL:
87 | DATABASES = {
88 | 'default': {
89 | 'ENGINE': 'django.db.backends.postgresql_psycopg2',
90 | 'NAME': 'game_db',
91 | 'USER': 'postgres',
92 | 'PASSWORD': 'postgres',
93 | 'HOST': 'localhost',
94 | 'PORT': '',
95 | }
96 | }
97 | else:
98 | DATABASES = {
99 | 'default': {
100 | 'ENGINE': 'django.db.backends.sqlite3',
101 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
102 | }
103 | }
104 |
105 | AUTH_PASSWORD_VALIDATORS = [
106 | {
107 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
108 | },
109 | {
110 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
111 | },
112 | {
113 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
114 | },
115 | {
116 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
117 | },
118 | ]
119 |
120 | # Localization
121 | LANGUAGE_CODE = 'pl'
122 | LANGUAGES = (
123 | ('en', _('English')),
124 | ('pl', _('Polish')),
125 | )
126 | TIME_ZONE = 'Europe/Warsaw'
127 | USE_I18N = True
128 | USE_L10N = True
129 | USE_TZ = True
130 | LOCALE_PATHS = (os.path.join(BASE_DIR, 'locale'),)
131 |
132 | MEDIA_URL = '/media/'
133 | MEDIA_ROOT = (os.path.join(BASE_DIR, 'media'))
134 | TEMPLATE_DIRS = (os.path.join(BASE_DIR, 'templates'),)
135 |
136 | SESSION_EXPIRE_AT_BROWSER_CLOSE = True
137 |
138 | LOGIN_URL = '/users/login'
139 | LOGIN_REDIRECT_URL = '/users/login'
140 |
141 | # Activate Django-Heroku.
142 | django_heroku.settings(locals())
143 |
144 | # internal_ips for django-debug-toolbar
145 | INTERNAL_IPS = '127.0.0.1'
146 |
147 | # CELERY CONFIG
148 | if DEBUG:
149 | BROKER_URL = 'redis://localhost:6379'
150 | CELERY_RESULT_BACKEND = 'redis://localhost:6379'
151 | else:
152 | BROKER_URL = config('BROKER_URL', default=False, cast=str)
153 | CELERY_RESULT_BACKEND = config('CELERY_RESULT_BACKEND', default=False, cast=str)
154 | CELERY_ACCEPT_CONTENT = ['application/json']
155 | CELERY_TASK_SERIALIZER = 'json'
156 | CELERY_RESULT_SERIALIZER = 'json'
157 | CELERY_TIMEZONE = 'Europe/Warsaw'
158 |
159 | # AWS CONFIG
160 | STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]
161 | if TRAVIS_ENVIRONMENT:
162 | STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
163 | STATIC_URL = '/static/'
164 | else:
165 | AWS_LOCATION = 'static'
166 | AWS_ACCESS_KEY_ID = config('AWS_ACCESS_KEY_ID', default=False, cast=str)
167 | AWS_SECRET_ACCESS_KEY = config('AWS_SECRET_ACCESS_KEY', default=False, cast=str)
168 | AWS_STORAGE_BUCKET_NAME = config('AWS_STORAGE_BUCKET_NAME', default=False, cast=str)
169 | AWS_S3_CUSTOM_DOMAIN = '%s.s3.amazonaws.com' % AWS_STORAGE_BUCKET_NAME
170 | AWS_S3_OBJECT_PARAMETERS = {
171 | 'CacheControl': 'max-age=86400',
172 | }
173 | DEFAULT_FILE_STORAGE = 'final_project_coderslab.storage_backends.MediaStorage'
174 | STATICFILES_STORAGE = "storages.backends.s3boto3.S3Boto3Storage"
175 | STATIC_URL = 'https://%s/%s/' % (AWS_S3_CUSTOM_DOMAIN, AWS_LOCATION)
176 | ADMIN_MEDIA_PREFIX = STATIC_URL + 'admin/'
177 | STATICFILES_FINDERS = (
178 | 'django.contrib.staticfiles.finders.FileSystemFinder',
179 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
180 | )
181 | AWS_DEFAULT_ACL = None
182 |
--------------------------------------------------------------------------------
/final_project_coderslab/storage_backends.py:
--------------------------------------------------------------------------------
1 | from storages.backends.s3boto3 import S3Boto3Storage
2 |
3 |
4 | class MediaStorage(S3Boto3Storage):
5 | location = 'media'
6 | file_overwrite = False
7 |
--------------------------------------------------------------------------------
/final_project_coderslab/urls.py:
--------------------------------------------------------------------------------
1 | import debug_toolbar
2 | from django.conf import settings
3 | from django.conf.urls.i18n import i18n_patterns
4 | from django.conf.urls.static import static
5 | from django.contrib import admin
6 | from django.urls import include, path
7 |
8 | urlpatterns = []
9 |
10 | urlpatterns += i18n_patterns(
11 | path('admin/', admin.site.urls),
12 | path('', include("news_api.urls", namespace="news_api-mainpage")),
13 | path('developer/', include("developer.urls", namespace="developer")),
14 | path('game/', include("game.urls", namespace="game")),
15 | path('genre/', include("genre.urls", namespace="genre")),
16 | path('main_app/', include("main_app.urls", namespace="main_app")),
17 | path('news_api/', include("news_api.urls", namespace="news_api")),
18 | path('recommendation/', include("game_recommendation.urls", namespace="game_recommendation")),
19 | path('tag/', include("tag.urls", namespace="tag")),
20 | path('users/', include("users.urls", namespace="users")),
21 | path('rest_api/', include("rest_api.urls", namespace="rest_api"))
22 | )
23 | if settings.DEBUG:
24 | urlpatterns = [path('__debug__/', include(debug_toolbar.urls)), ] + urlpatterns
25 | urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
26 |
--------------------------------------------------------------------------------
/final_project_coderslab/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for projekt_koncowy 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/2.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", "final_project_coderslab.settings")
15 |
16 | application = get_wsgi_application()
17 |
--------------------------------------------------------------------------------
/game/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dawidbudzynski/gameweb_python_django/b95719f04b43d2cd0c804e5972a43648e05d9e39/game/__init__.py
--------------------------------------------------------------------------------
/game/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 |
3 | from .models import Game, GameScore
4 |
5 | admin.site.register(Game)
6 | admin.site.register(GameScore)
7 |
--------------------------------------------------------------------------------
/game/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class GameConfig(AppConfig):
5 | name = 'game'
6 |
--------------------------------------------------------------------------------
/game/forms.py:
--------------------------------------------------------------------------------
1 | from django.forms import (CharField, CheckboxSelectMultiple, ChoiceField, Form, ImageField, ModelChoiceField,
2 | ModelMultipleChoiceField, NullBooleanField, Select)
3 |
4 | from constants import SORTED_RATING, SORTED_YEARS
5 | from django.utils.translation import ugettext_lazy as _
6 | from developer.models import Developer
7 | from genre.models import Genre
8 | from tag.models import Tag
9 |
10 | all_tags = Tag.objects.all()
11 | all_developers = Developer.objects.all()
12 | all_genres = Genre.objects.all()
13 |
14 |
15 | class RateGameForm(Form):
16 | score = ChoiceField(choices=SORTED_RATING)
17 |
18 |
19 | class AddGameForm(Form):
20 | title = CharField(max_length=64)
21 | year = ChoiceField(choices=SORTED_YEARS)
22 | developer = ModelChoiceField(
23 | queryset=all_developers.order_by('name'),
24 | widget=Select
25 | )
26 | genre = ModelChoiceField(
27 | queryset=all_genres.order_by('name'),
28 | widget=Select
29 | )
30 | tags = ModelMultipleChoiceField(
31 | label=_('Tags (Select 6)'),
32 | queryset=all_tags.order_by('name'),
33 | widget=CheckboxSelectMultiple(attrs={'class': 'checkboxmultiple'})
34 | )
35 | image = ImageField(required=False)
36 | to_be_rated = NullBooleanField(label=_('Should be rated?'), required=False)
37 |
--------------------------------------------------------------------------------
/game/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.1.2 on 2019-05-18 10:16
2 |
3 | from django.db import migrations, models
4 | import django.db.models.deletion
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | initial = True
10 |
11 | dependencies = [
12 | ('tag', '0001_initial'),
13 | ('genre', '0001_initial'),
14 | ('developer', '0001_initial'),
15 | ('users', '0001_initial'),
16 | ]
17 |
18 | operations = [
19 | migrations.CreateModel(
20 | name='Game',
21 | fields=[
22 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
23 | ('title', models.CharField(max_length=64)),
24 | ('year', models.IntegerField(choices=[(2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998)])),
25 | ('image', models.ImageField(blank=True, upload_to='images/')),
26 | ('to_be_rated', models.NullBooleanField(default=False)),
27 | ('developer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='developer.Developer')),
28 | ('genre', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='genre.Genre')),
29 | ('tags', models.ManyToManyField(to='tag.Tag')),
30 | ],
31 | ),
32 | migrations.CreateModel(
33 | name='GameScore',
34 | fields=[
35 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
36 | ('score', models.IntegerField(blank=True, choices=[(10, '**********'), (9, '*********'), (8, '********'), (7, '*******'), (6, '******'), (5, '*****'), (4, '****'), (3, '***'), (2, '**'), (1, '*')])),
37 | ('game', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='game.Game')),
38 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.User')),
39 | ],
40 | ),
41 | ]
42 |
--------------------------------------------------------------------------------
/game/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dawidbudzynski/gameweb_python_django/b95719f04b43d2cd0c804e5972a43648e05d9e39/game/migrations/__init__.py
--------------------------------------------------------------------------------
/game/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
3 | from constants import SORTED_RATING, SORTED_YEARS
4 | from developer.models import Developer
5 | from genre.models import Genre
6 | from tag.models import Tag
7 | from users.models import User
8 |
9 |
10 | class Game(models.Model):
11 | title = models.CharField(max_length=64)
12 | year = models.IntegerField(choices=SORTED_YEARS)
13 | developer = models.ForeignKey(Developer, on_delete=models.CASCADE)
14 | genre = models.ForeignKey(Genre, on_delete=models.CASCADE)
15 | tags = models.ManyToManyField(Tag)
16 | image = models.ImageField(upload_to='images/', blank=True)
17 | to_be_rated = models.NullBooleanField(null=True, default=False)
18 |
19 | def __str__(self):
20 | return self.title
21 |
22 |
23 | class GameScore(models.Model):
24 | user = models.ForeignKey(User, on_delete=models.CASCADE)
25 | game = models.ForeignKey(Game, on_delete=models.CASCADE)
26 | score = models.IntegerField(choices=SORTED_RATING, blank=True)
27 |
28 | def __str__(self):
29 | return '{}-{}-{}'.format(self.user, self.game, self.score)
30 |
--------------------------------------------------------------------------------
/game/templates/game_create.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load bootstrap4 i18n staticfiles %}
3 | {% block content %}
4 |
5 |
6 |
7 | {% trans 'Add game' %} |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
19 | |
20 |
21 |
22 |
23 | {% endblock %}
24 |
--------------------------------------------------------------------------------
/game/templates/game_details.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load bootstrap4 i18n staticfiles %}
3 | {% block content %}
4 |
5 |
6 |
7 | {{ game.title }} |
8 |
9 |
10 |
11 |
12 | {% trans 'Genre' %} |
13 |
14 | {% for genre in game.genre.all %}
15 |
16 | {% endfor %}
17 | |
18 |
19 |  |
20 |
21 |
22 | {% trans 'Developer' %} |
23 | |
25 |
26 |
27 | {% trans 'Tags' %} |
28 |
29 | {% for tag in game.tags.all %}
30 | {{ tag.name }}
31 | {% endfor %}
32 | |
33 |
34 |
35 | {% trans 'Year' %} |
36 | {{ game.year }} |
37 |
38 |
39 | {% trans 'Rating' %} |
40 |
41 | {% if gamescore %}
42 | {{ gamescore.score }}
43 | {% else %}
44 | {% trans 'No rating' %}
45 | {% endif %}
46 | |
47 |
48 | {% if user.is_authenticated %}
49 |
50 | {% trans 'Rate game' %} |
51 |
52 |
57 | |
58 |
59 | {% endif %}
60 | {% if user.is_staff %}
61 |
62 |
63 | {% trans 'Delete' %}
65 | |
66 | |
67 | {% endif %}
68 |
69 |
70 | {% endblock %}
71 |
--------------------------------------------------------------------------------
/game/templates/game_list.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load bootstrap4 i18n staticfiles %}
3 | {% block content %}
4 |
5 |
6 |
7 | {% trans 'Games' %} |
8 |
9 |
10 |
11 | {% if user.is_authenticated %}
12 |
13 |
14 |
15 | {% trans 'Add game' %}
17 |
18 | |
19 |
20 |
21 | {% endif %}
22 |
23 | {% include 'message_block.html' %}
24 | {% for game in object_list %}
25 |
26 |
27 |
28 | {{ game.title }} |
29 |
30 |
31 |
32 |
33 | {% trans 'Genre' %} |
34 |
35 | {% for genre in game.genre.all %}
36 |
37 | {% endfor %}
38 | |
39 | {% if game.image %}
40 |
41 |
43 | |
44 | {% else %}
45 |
46 |
48 | |
49 | {% endif %}
50 |
51 |
52 | {% trans 'Developer' %} |
53 |
55 | |
56 |
57 |
58 | {% trans 'Tags' %} |
59 |
60 | {% for tag in game.tags.all %}
61 | {{ tag.name }}
62 | {% endfor %}
63 | |
64 |
65 |
66 | {% trans 'Year' %} |
67 | {{ game.year }} |
68 |
69 |
70 |
71 | {% trans 'Details' %}
73 | |
74 |
75 | {% if user.is_staff %}
76 |
77 |
78 | {% trans 'Delete' %}
80 | |
81 |
82 | {% endif %}
83 |
84 |
85 | {% endfor %}
86 | {% endblock %}
87 |
--------------------------------------------------------------------------------
/game/tests.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from django.conf import settings
4 | from django.contrib.auth.models import User as DjangoUser
5 | from django.test import TestCase
6 | from django.urls import reverse
7 |
8 | from developer.models import Developer
9 | from game.models import Game
10 | from genre.models import Genre
11 | from tag.models import Tag
12 | from users.models import User
13 | from .forms import AddGameForm
14 |
15 |
16 | class GameFormTests(TestCase, unittest.TestCase):
17 |
18 | def setUp(self):
19 | django_user = DjangoUser.objects.create(
20 | username='user_1',
21 | password='password_1',
22 | first_name='first_name_1',
23 | last_name='last_name_1',
24 | email='email_1'
25 | )
26 | self.user = User.objects.create(user=django_user)
27 |
28 | Genre.objects.create(name='genre_1')
29 | Developer.objects.create(name='developer_1')
30 | for i in range(1, 7):
31 | Tag.objects.create(name=f'tag_{i}')
32 |
33 | game_1 = Game.objects.create(
34 | developer=Developer.objects.get(name='developer_1'),
35 | genre=Genre.objects.get(name='genre_1'),
36 | title='game_1',
37 | year=2015,
38 | to_be_rated=False
39 | )
40 | for tag in Tag.objects.all():
41 | game_1.tags.add(tag)
42 | game_1.save()
43 |
44 | def test_game_form(self):
45 | # redirects to login screen
46 | response = self.client.get(reverse('game:game-create'))
47 | self.assertEqual('/users/login?next=/pl/game/game_create/', response['location'])
48 | self.assertEquals(response.status_code, 302)
49 |
50 | self.client.force_login(DjangoUser.objects.get_or_create(username='user_1')[0])
51 | response = self.client.get(reverse('game:game-create'))
52 | self.assertEquals(response.status_code, 200)
53 |
54 | form = AddGameForm()
55 | self.assertFalse(form.is_valid())
56 |
57 | data = {"name": ""}
58 | form = AddGameForm(data=data)
59 | self.assertFalse(form.is_valid())
60 | self.assertEqual(form.errors, {
61 | 'developer': ['To pole jest wymagane.'],
62 | 'genre': ['To pole jest wymagane.'],
63 | 'tags': ['To pole jest wymagane.'],
64 | 'title': ['To pole jest wymagane.'],
65 | 'year': ['To pole jest wymagane.']
66 | })
67 |
68 | data = {
69 | 'developer': Developer.objects.get(name='developer_1').id,
70 | 'genre': Genre.objects.get(name='genre_1').id,
71 | 'tags': [
72 | Tag.objects.get(name='tag_1'),
73 | Tag.objects.get(name='tag_2'),
74 | Tag.objects.get(name='tag_3'),
75 | Tag.objects.get(name='tag_4'),
76 | Tag.objects.get(name='tag_5'),
77 | Tag.objects.get(name='tag_6')
78 | ],
79 | 'title': 'game_1',
80 | 'year': 2015,
81 | 'to_be_rated': False
82 | }
83 | form = AddGameForm(data=data)
84 | self.assertTrue(form.is_valid())
85 |
86 | def test_created_object(self):
87 | game_object = Game.objects.get(title='game_1')
88 | developer_id = 5 if (settings.WORK_ON_POSTGRE_SQL or settings.TRAVIS_ENVIRONMENT) else 1
89 | self.assertDictContainsSubset({
90 | 'developer_id': developer_id,
91 | 'genre_id': 1,
92 | 'id': 1,
93 | 'image': '',
94 | 'title': 'game_1',
95 | 'to_be_rated': False,
96 | 'year': 2015},
97 | game_object.__dict__
98 | )
99 |
100 | def test_game_list_view(self):
101 | response = self.client.get(reverse('game:game-list'))
102 | self.assertEqual(response.status_code, 200)
103 | self.assertContains(response, 'game_1')
104 | self.assertEqual(len(response.context[0]['object_list']), 1)
105 | self.assertTemplateUsed(response, 'game_list.html')
106 |
--------------------------------------------------------------------------------
/game/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import path
2 |
3 | from . import views
4 |
5 | app_name = 'game'
6 |
7 | urlpatterns = [
8 | path('game_create/', views.GameCreateView.as_view(),
9 | name='game-create'),
10 | path('game_list/', views.GameListView.as_view(),
11 | name='game-list'),
12 | path('game_details/', views.GameDetails.as_view(),
13 | name='game-details'),
14 | path('game_delete/', views.GameDeleteView.as_view(),
15 | name='game-delete'),
16 | ]
17 |
--------------------------------------------------------------------------------
/game/views.py:
--------------------------------------------------------------------------------
1 | from django.contrib import messages
2 | from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
3 | from django.http import HttpResponseRedirect
4 | from django.shortcuts import render
5 | from django.urls import reverse
6 | from django.utils.translation import ugettext as _
7 | from django.views import View
8 | from django.views.generic import ListView
9 |
10 | from users.models import User
11 | from .forms import AddGameForm, RateGameForm
12 | from .models import Game, GameScore
13 |
14 |
15 | class GameListView(ListView):
16 | model = Game
17 | template_name = 'game_list.html'
18 |
19 |
20 | class GameCreateView(LoginRequiredMixin, View):
21 | def get(self, request):
22 | return render(
23 | request,
24 | template_name='game_create.html',
25 | context={'form': AddGameForm().as_p()}
26 | )
27 |
28 | def post(self, request):
29 | form = AddGameForm(request.POST, request.FILES)
30 | if form.is_valid():
31 | title = form.cleaned_data['title']
32 | year = form.cleaned_data['year']
33 | developer = form.cleaned_data['developer']
34 | genre = form.cleaned_data['genre']
35 | tags = form.cleaned_data['tags']
36 | image = form.cleaned_data['image']
37 | to_be_rated = form.cleaned_data['to_be_rated']
38 |
39 | if Game.objects.filter(title=title).exists():
40 | messages.add_message(request, messages.WARNING, _('Game with this name already exists'))
41 | return HttpResponseRedirect(reverse('game:game-create'))
42 |
43 | new_game = Game.objects.create(
44 | title=title,
45 | year=year,
46 | developer=developer,
47 | genre=genre,
48 | image=image,
49 | to_be_rated=to_be_rated
50 | )
51 |
52 | for tag in tags:
53 | new_game.tags.add(tag)
54 |
55 | messages.add_message(request, messages.INFO, _('Game: {} created successfully').format(title))
56 | return HttpResponseRedirect(reverse('game:game-list'))
57 |
58 | messages.add_message(request, messages.ERROR, _('Form invalid'))
59 | return HttpResponseRedirect(reverse('game:game-create'))
60 |
61 |
62 | class GameDetails(View):
63 | def get(self, request, game_id):
64 | game = Game.objects.get(id=game_id)
65 | user = User.objects.get(id=request.user.id)
66 | gamescore_qs = GameScore.objects.filter(game=game, user=user)
67 | gamescore = gamescore_qs.first() if gamescore_qs.exists() else None
68 |
69 | ctx = {
70 | 'game': game,
71 | 'gamescore': gamescore,
72 | 'form': RateGameForm()
73 | }
74 |
75 | return render(
76 | request,
77 | template_name='game_details.html',
78 | context=ctx
79 | )
80 |
81 | def post(self, request, game_id):
82 | """Get rating from form and create or update gamescore"""
83 | form = RateGameForm(request.POST)
84 | try:
85 | user = User.objects.get(user=request.user)
86 | except User.DoesNotExist:
87 | messages.add_message(request, messages.INFO, _('User {} can\'t rate games').format(request.user))
88 | return HttpResponseRedirect(reverse('game:game-list'))
89 | game = Game.objects.get(id=game_id)
90 | try:
91 | old_gamescore = GameScore.objects.get(game=game, user=user)
92 | except GameScore.DoesNotExist:
93 | old_gamescore = None
94 | if form.is_valid():
95 | score = form.cleaned_data['score']
96 | if old_gamescore is not None:
97 | old_gamescore.score = score
98 | old_gamescore.save()
99 | gamescore = old_gamescore
100 | else:
101 | gamescore = GameScore.objects.create(
102 | user=user,
103 | game=game,
104 | score=score
105 | )
106 |
107 | ctx = {
108 | 'game': game,
109 | 'form': form,
110 | 'gamescore': gamescore
111 | }
112 |
113 | return render(
114 | request,
115 | template_name='game_details.html',
116 | context=ctx
117 | )
118 |
119 |
120 | class GameDeleteView(PermissionRequiredMixin, View):
121 | permission_required = 'game.delete_game'
122 | raise_exception = True
123 |
124 | def get(self, request, game_id):
125 | game = Game.objects.get(id=game_id)
126 | game.delete()
127 | return HttpResponseRedirect(reverse('game:game-list'))
128 |
--------------------------------------------------------------------------------
/game_recommendation/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dawidbudzynski/gameweb_python_django/b95719f04b43d2cd0c804e5972a43648e05d9e39/game_recommendation/__init__.py
--------------------------------------------------------------------------------
/game_recommendation/admin.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dawidbudzynski/gameweb_python_django/b95719f04b43d2cd0c804e5972a43648e05d9e39/game_recommendation/admin.py
--------------------------------------------------------------------------------
/game_recommendation/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class GameRecommendationConfig(AppConfig):
5 | name = 'game_recommendation'
6 |
--------------------------------------------------------------------------------
/game_recommendation/forms.py:
--------------------------------------------------------------------------------
1 | from django.forms import (CheckboxSelectMultiple, Form, ModelChoiceField, ModelMultipleChoiceField, Select)
2 | from django.utils.translation import ugettext_lazy as _
3 |
4 | from developer.models import Developer
5 | from genre.models import Genre
6 | from tag.models import Tag
7 |
8 | all_tags = Tag.objects.all()
9 | all_developers = Developer.objects.all()
10 | all_genres = Genre.objects.all()
11 |
12 |
13 | class ChooseTagsForm(Form):
14 | genre = ModelChoiceField(
15 | queryset=all_genres.order_by('name'),
16 | widget=Select
17 | )
18 | developer = ModelChoiceField(
19 | queryset=all_developers.order_by('name'),
20 | widget=Select
21 | )
22 | tags = ModelMultipleChoiceField(
23 | label=_('Tags (Select 6)'),
24 | queryset=all_tags.order_by('name'),
25 | widget=CheckboxSelectMultiple(attrs={'class': 'checkboxmultiple'})
26 | )
27 |
--------------------------------------------------------------------------------
/game_recommendation/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dawidbudzynski/gameweb_python_django/b95719f04b43d2cd0c804e5972a43648e05d9e39/game_recommendation/migrations/__init__.py
--------------------------------------------------------------------------------
/game_recommendation/models.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dawidbudzynski/gameweb_python_django/b95719f04b43d2cd0c804e5972a43648e05d9e39/game_recommendation/models.py
--------------------------------------------------------------------------------
/game_recommendation/templates/recommend_by_rated_games.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load bootstrap4 i18n staticfiles %}
3 | {% block content %}
4 |
44 | {% endblock %}
45 |
--------------------------------------------------------------------------------
/game_recommendation/templates/recommend_by_rated_games_result.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load bootstrap4 i18n staticfiles %}
3 | {% block content %}
4 |
5 |
6 |
7 | {% trans 'According to your answers' %} |
8 |
9 |
10 | {% trans 'Favorite genre' %} |
11 | {% trans 'Favorite developer' %} |
12 | {% trans 'Favorite tags' %} |
13 |
14 |
15 |
16 | {% for genre in user_favorites.favorite_genre %}
17 |
18 | {{ genre.name }}
19 | {% endfor %}
20 | |
21 |
22 | {% for developer in user_favorites.favorite_developer %}
23 |
24 | {{ developer.name }}
25 | {% endfor %}
26 | |
27 |
28 | {% for tag in user_favorites.favorite_tags_top_6 %}
29 | {{ tag.name }}
30 | {% endfor %}
31 | |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | {% trans 'Games you may like' %} |
40 |
41 |
42 |
43 | {% for single_game_info in sorted_all_game_information %}
44 |
138 | {% endfor %}
139 | {% endblock %}
140 |
--------------------------------------------------------------------------------
/game_recommendation/templates/recommend_games_manually.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load bootstrap4 i18n staticfiles %}
3 | {% block content %}
4 |
5 |
6 |
7 | {% trans 'Choose your preferences' %} |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
19 | |
20 |
21 |
22 |
23 | {% endblock %}
24 |
--------------------------------------------------------------------------------
/game_recommendation/templates/recommend_games_manually_result.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load bootstrap4 i18n staticfiles %}
3 | {% block content %}
4 |
5 |
6 |
7 | {% trans 'According to your answers' %} |
8 |
9 |
10 | {% trans 'Favorite game' %} |
11 | {% trans 'Favorite developer' %} |
12 | {% trans 'Favorite tags' %} |
13 |
14 |
15 |
17 | |
18 |
20 | |
21 |
22 | {% for tag in user_favorites.favorite_tags_top_6 %}
23 | {{ tag.name }}
24 | {% endfor %}
25 | |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | {% trans 'Games you may like' %} |
34 |
35 |
36 |
37 | {% for single_game_information in sorted_all_game_information %}
38 |
130 | {% endfor %}
131 | {% endblock %}
132 |
--------------------------------------------------------------------------------
/game_recommendation/tests.py:
--------------------------------------------------------------------------------
1 | import random
2 | import unittest
3 |
4 | from django.test import TestCase
5 | from django.urls import reverse
6 |
7 | from developer.models import Developer
8 | from genre.models import Genre
9 | from tag.models import Tag
10 | from utils.tests import populate_database
11 | from .forms import ChooseTagsForm
12 |
13 |
14 | class GameRecommendationTests(TestCase, unittest.TestCase):
15 |
16 | def setUp(self):
17 | populate_database()
18 |
19 | def test_recommendation_manually_content(self):
20 | response = self.client.get(reverse('game_recommendation:recommend-manually'))
21 | self.assertEquals(response.status_code, 200)
22 |
23 | form = ChooseTagsForm()
24 | self.assertFalse(form.is_valid())
25 |
26 | data = {"name": ""}
27 | form = ChooseTagsForm(data=data)
28 | self.assertFalse(form.is_valid())
29 | self.assertEqual(
30 | form.errors,
31 | {'developer': ['To pole jest wymagane.'],
32 | 'genre': ['To pole jest wymagane.'],
33 | 'tags': ['To pole jest wymagane.']}
34 | )
35 |
36 | tag_list = []
37 | for i in random.sample(range(1, 21), 6):
38 | tag_list.append(Tag.objects.get(name=f'tag_{i}'))
39 |
40 | data = {
41 | 'developer': Developer.objects.get(name='developer_7').id,
42 | 'genre': Genre.objects.get(name='genre_9').id,
43 | 'tags': tag_list
44 | }
45 | form = ChooseTagsForm(data=data)
46 | self.assertTrue(form.is_valid())
47 |
--------------------------------------------------------------------------------
/game_recommendation/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import path
2 |
3 | from . import views
4 |
5 | app_name = 'game_recommendation'
6 |
7 | urlpatterns = [
8 | path('recommend_game_manually', views.RecommendGamesManuallyView.as_view(),
9 | name='recommend-manually'),
10 | path('recommend_game_by_rating', views.RecommendByRatedGamesView.as_view(),
11 | name='recommend-by-rating')
12 | ]
13 |
--------------------------------------------------------------------------------
/game_recommendation/views.py:
--------------------------------------------------------------------------------
1 | from collections import Counter
2 | from operator import itemgetter
3 |
4 | from django.contrib import messages
5 | from django.http import HttpResponseRedirect
6 | from django.shortcuts import render
7 | from django.urls import reverse
8 | from django.utils.translation import ugettext as _
9 | from django.views import View
10 |
11 | from game.models import Game
12 | from .forms import ChooseTagsForm
13 |
14 | DEVELOPER_MATCH_SCORE = 14
15 | GENRE_MATCH_SCORE = 14
16 | TAG_MATCH_SCORE = 12
17 |
18 |
19 | class RecommendGamesManuallyView(View):
20 | def get(self, request):
21 | return render(
22 | request,
23 | template_name='recommend_games_manually.html',
24 | context={'form': ChooseTagsForm().as_p()}
25 | )
26 |
27 | def post(self, request):
28 | all_games_qs = Game.objects.all()
29 | form = ChooseTagsForm(request.POST)
30 | if form.is_valid():
31 | user_genre = form.cleaned_data['genre']
32 | user_developer = form.cleaned_data['developer']
33 | user_tags = form.cleaned_data['tags']
34 |
35 | all_games_info = []
36 |
37 | for game in self._assign_matching_tags_to_game(user_tags, all_games_qs):
38 | game_object = game['game_object']
39 | game_info = {
40 | 'game': game_object,
41 | 'matched_tags': [],
42 | 'unmatched_tags': []
43 | }
44 | match_score = 0
45 |
46 | if game_object.developer == user_developer:
47 | game_info['developer_match'] = True
48 | match_score += DEVELOPER_MATCH_SCORE
49 | else:
50 | game_info['developer_match'] = False
51 |
52 | if game_object.genre == user_genre:
53 | game_info['genre_match'] = True
54 | match_score += GENRE_MATCH_SCORE
55 | else:
56 | game_info['genre_match'] = False
57 |
58 | for game_tag in game_object.tags.all():
59 | if game_tag in game['matching_tags']:
60 | match_score += TAG_MATCH_SCORE
61 | game_info['matched_tags'].append(game_tag)
62 | else:
63 | game_info['unmatched_tags'].append(game_tag)
64 |
65 | game_info['match_score'] = match_score
66 | all_games_info.append(game_info)
67 |
68 | ctx = {
69 | 'sorted_all_game_information': sorted(
70 | all_games_info, key=itemgetter('match_score'), reverse=True
71 | ),
72 | 'user_favorites': {
73 | 'favorite_tags_top_6': user_tags,
74 | 'favorite_genre': user_genre,
75 | 'favorite_developer': user_developer
76 | }
77 | }
78 |
79 | return render(
80 | request,
81 | template_name='recommend_games_manually_result.html',
82 | context=ctx
83 | )
84 |
85 | messages.add_message(request, messages.ERROR, _('Form invalid'))
86 | return HttpResponseRedirect(reverse('game_recommendation:recommend-manually'))
87 |
88 | @staticmethod
89 | def _assign_matching_tags_to_game(user_tags, all_games_qs):
90 | """Returns all game objects with game tags which match user's favorites"""
91 | games_and_matching_tags = []
92 | for game in all_games_qs:
93 | matching_tags = list(set(game.tags.all()).intersection(user_tags))
94 | games_and_matching_tags.append({
95 | 'game_object': game,
96 | 'matching_tags': matching_tags
97 | })
98 | return games_and_matching_tags
99 |
100 |
101 | class RecommendByRatedGamesView(View):
102 | def get(self, request):
103 | return render(
104 | request,
105 | template_name='recommend_by_rated_games.html',
106 | context={'to_be_rated': Game.objects.filter(to_be_rated=True)}
107 | )
108 |
109 | def post(self, request):
110 | games_rated = Game.objects.filter(to_be_rated=True)
111 | liked_games = [game for game in games_rated if request.POST["game_{}".format(game.id)] == 'like']
112 |
113 | liked_tags = []
114 | liked_genres = []
115 | liked_developers = []
116 | for game in liked_games:
117 | liked_tags = list(game.tags.all())
118 | liked_genres.append(game.genre)
119 | liked_developers.append(game.developer)
120 |
121 | favorite_tags = self.find_best_of_category(liked_tags, 6)
122 | favorite_genre = self.find_best_of_category(liked_genres, 1)
123 | favorite_developer = self.find_best_of_category(liked_developers, 1)
124 |
125 | all_games_info = []
126 | games_not_rated = list(set(Game.objects.all()) - set(games_rated))
127 | for game_object in games_not_rated:
128 | game_info = {
129 | 'game': game_object,
130 | 'matched_tags': [],
131 | 'unmatched_tags': []
132 | }
133 | match_score = 0
134 |
135 | game_tags = list(game_object.tags.all())
136 | matching_tags = list(set(game_tags).intersection(favorite_tags))
137 | match_score += TAG_MATCH_SCORE * len(matching_tags)
138 | game_info['matched_tags'] = matching_tags
139 | game_info['unmatched_tags'] = [tag for tag in game_tags if tag not in matching_tags]
140 |
141 | if game_object.genre in favorite_genre:
142 | game_info['genre_match'] = True
143 | match_score += GENRE_MATCH_SCORE
144 | else:
145 | game_info['genre_match'] = False
146 |
147 | if game_object.developer in favorite_developer:
148 | game_info['developer_match'] = True
149 | match_score += DEVELOPER_MATCH_SCORE
150 | else:
151 | game_info['developer_match'] = False
152 |
153 | game_info['match_score'] = match_score
154 |
155 | all_games_info.append(game_info)
156 |
157 | ctx = {
158 | 'sorted_all_game_information': sorted(
159 | all_games_info, key=itemgetter('match_score'), reverse=True
160 | ),
161 | 'user_favorites': {
162 | 'favorite_tags_top_6': favorite_tags,
163 | 'favorite_genre': favorite_genre,
164 | 'favorite_developer': favorite_developer
165 | }
166 | }
167 |
168 | return render(
169 | request,
170 | template_name='recommend_by_rated_games_result.html',
171 | context=ctx
172 | )
173 |
174 | @staticmethod
175 | def find_best_of_category(category, number_of_items):
176 | result = []
177 | counted_items = Counter(category).items()
178 | for element in sorted(counted_items, key=lambda x: x[1], reverse=True)[:number_of_items]:
179 | result.append(element[0])
180 | return result
181 |
--------------------------------------------------------------------------------
/genre/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dawidbudzynski/gameweb_python_django/b95719f04b43d2cd0c804e5972a43648e05d9e39/genre/__init__.py
--------------------------------------------------------------------------------
/genre/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 |
3 | from .models import Genre
4 |
5 | admin.site.register(Genre)
6 |
--------------------------------------------------------------------------------
/genre/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class GenreConfig(AppConfig):
5 | name = 'genre'
6 |
--------------------------------------------------------------------------------
/genre/forms.py:
--------------------------------------------------------------------------------
1 | from django.forms import (CharField, Form)
2 | from django.utils.translation import ugettext_lazy as _
3 |
4 |
5 | class AddGenreForm(Form):
6 | name = CharField(max_length=64, label=_('Name'))
7 |
--------------------------------------------------------------------------------
/genre/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.1.2 on 2019-05-18 10:16
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | initial = True
9 |
10 | dependencies = [
11 | ]
12 |
13 | operations = [
14 | migrations.CreateModel(
15 | name='Genre',
16 | fields=[
17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18 | ('name', models.CharField(max_length=64)),
19 | ],
20 | ),
21 | ]
22 |
--------------------------------------------------------------------------------
/genre/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dawidbudzynski/gameweb_python_django/b95719f04b43d2cd0c804e5972a43648e05d9e39/genre/migrations/__init__.py
--------------------------------------------------------------------------------
/genre/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
3 |
4 | class Genre(models.Model):
5 | name = models.CharField(max_length=64)
6 |
7 | def __str__(self):
8 | return self.name
9 |
--------------------------------------------------------------------------------
/genre/templates/games_by_genre.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load bootstrap4 i18n staticfiles %}
3 | {% block content %}
4 |
5 |
6 |
7 | {% trans 'Games with genre: ' %}{{ selected_genre.name }} |
8 |
9 |
10 |
11 | {% for game in all_games_with_genre %}
12 |
13 |
14 |
15 | {{ game.title }} |
16 |
17 |
18 |
19 |
20 | {% trans 'Genre' %} |
21 |
22 | {% for genre in game.genre.all %}
23 |
26 | {% endfor %}
27 | |
28 |
29 |
30 | |
31 |
32 |
33 | {% trans 'Developer' %} |
34 |
36 | |
37 |
38 |
39 | {% trans 'Tags' %} |
40 |
41 | {% for tag in game.tags.all %}
42 | {{ tag.name }}
43 | {% endfor %}
44 | |
45 |
46 |
47 | {% trans 'Year' %} |
48 | {{ game.year }} |
49 |
50 |
51 |
52 | {% trans 'Delete' %}
54 | |
55 |
56 |
57 |
58 | {% endfor %}
59 | {% endblock %}
60 |
--------------------------------------------------------------------------------
/genre/templates/genre_create.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load bootstrap4 i18n staticfiles %}
3 | {% block content %}
4 |
5 |
6 |
7 | {% trans 'Create genre' %} |
8 |
9 |
10 |
11 |
12 | {% include 'message_block.html' %}
13 |
14 |
15 |
20 | |
21 |
22 |
23 | {% endblock %}
24 |
--------------------------------------------------------------------------------
/genre/templates/genre_list.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load bootstrap4 i18n staticfiles %}
3 | {% block content %}
4 |
5 |
6 |
7 | {% trans 'Genre' %} |
8 |
9 |
10 |
11 | {% if user.is_authenticated %}
12 |
13 |
14 |
15 | {% trans 'Create genre' %}
17 | |
18 |
19 |
20 | {% endif %}
21 |
22 | {% include 'message_block.html' %}
23 | {% for genre in object_list %}
24 |
25 |
26 |
27 |
28 |
43 | |
44 |
45 |
46 |
47 | {% endfor %}
48 | {% endblock %}
49 |
--------------------------------------------------------------------------------
/genre/tests.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth.models import User
2 | from django.test import TestCase
3 | from django.urls import reverse
4 |
5 | from genre.models import Genre
6 | from .forms import AddGenreForm
7 |
8 |
9 | class GenreFormTests(TestCase):
10 |
11 | def test_genre_form(self):
12 | # redirects to login screen
13 | response = self.client.get(reverse('genre:genre-create'))
14 | self.assertEqual('/users/login?next=/pl/genre/genre_create/', response['location'])
15 | self.assertEquals(response.status_code, 302)
16 |
17 | self.client.force_login(User.objects.get_or_create(username='user_1')[0])
18 | response = self.client.get(reverse('genre:genre-create'))
19 | self.assertEquals(response.status_code, 200)
20 |
21 | form = AddGenreForm()
22 | self.assertFalse(form.is_valid())
23 |
24 | data = {"name": ""}
25 | form = AddGenreForm(data=data)
26 | self.assertFalse(form.is_valid())
27 | self.assertEqual(form.errors, {'name': ['To pole jest wymagane.']})
28 |
29 | data = {"name": "genre_1"}
30 | form = AddGenreForm(data=data)
31 | self.assertTrue(form.is_valid())
32 |
33 |
34 | class GenreListTests(TestCase):
35 |
36 | def setUp(self):
37 | Genre.objects.create(name='genre_1')
38 | Genre.objects.create(name='genre_2')
39 |
40 | def test_text_content(self):
41 | genre_object = Genre.objects.get(name='genre_1')
42 | expected_object_name = f'{genre_object.name}'
43 | self.assertEquals(expected_object_name, 'genre_1')
44 |
45 | def test_genre_list_view(self):
46 | response = self.client.get(reverse('genre:genre-list'))
47 | self.assertEqual(response.status_code, 200)
48 | self.assertContains(response, 'genre_1')
49 | self.assertEqual(len(response.context[0]['object_list']), 2)
50 | self.assertTemplateUsed(response, 'genre_list.html')
51 |
--------------------------------------------------------------------------------
/genre/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import path
2 |
3 | from . import views
4 |
5 | app_name = 'genre'
6 |
7 | urlpatterns = [
8 | path('genre_create/', views.GenreCreateView.as_view(),
9 | name='genre-create'),
10 | path('genre_list/', views.GenreListView.as_view(),
11 | name='genre-list'),
12 | path('genre_delete/', views.GenreDeleteView.as_view(),
13 | name='genre-delete'),
14 | path('games_by_genre/', views.GamesByGenreView.as_view(),
15 | name='games-by-genre')
16 | ]
17 |
--------------------------------------------------------------------------------
/genre/views.py:
--------------------------------------------------------------------------------
1 | from decouple import config
2 | from django.contrib import messages
3 | from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
4 | from django.http import HttpResponseRedirect
5 | from django.shortcuts import render
6 | from django.urls import reverse
7 | from django.utils.translation import ugettext as _
8 | from django.views import View
9 | from django.views.generic import ListView
10 | from game.models import Game
11 |
12 | from .forms import AddGenreForm
13 | from .models import Genre
14 |
15 | NEWS_API_KEY = config('NEWS_API_KEY', cast=str)
16 |
17 |
18 | class GenreListView(ListView):
19 | model = Genre
20 | template_name = 'genre_list.html'
21 |
22 |
23 | class GenreCreateView(LoginRequiredMixin, View):
24 | def get(self, request):
25 | return render(
26 | request,
27 | template_name='genre_create.html',
28 | context={'form': AddGenreForm().as_p()}
29 | )
30 |
31 | def post(self, request):
32 | form = AddGenreForm(request.POST)
33 | if form.is_valid():
34 | name = form.cleaned_data['name']
35 |
36 | if Genre.objects.filter(name=name).exists():
37 | messages.add_message(request, messages.WARNING, _('Genre with this name already exists'))
38 | return HttpResponseRedirect(reverse('genre:genre-create'))
39 |
40 | Genre.objects.create(name=name)
41 | messages.add_message(request, messages.INFO, _('Genre: {} created successfully').format(name))
42 | return HttpResponseRedirect(reverse('genre:genre-list'))
43 |
44 | messages.add_message(request, messages.ERROR, _('Form invalid'))
45 | return HttpResponseRedirect(reverse('genre:genre-create'))
46 |
47 |
48 | class GenreDeleteView(PermissionRequiredMixin, View):
49 | permission_required = 'genre.delete_genre'
50 | raise_exception = True
51 |
52 | def get(self, request, genre_id):
53 | genre = Genre.objects.get(id=genre_id)
54 | genre.delete()
55 | return HttpResponseRedirect(reverse('genre:genre-list'))
56 |
57 |
58 | class GamesByGenreView(View):
59 | """Display all games with selected genre"""
60 |
61 | def get(self, request, genre_id):
62 | selected_genre = Genre.objects.get(id=genre_id)
63 | ctx = {
64 | 'all_games_with_genre': Game.objects.filter(genre=selected_genre),
65 | 'selected_genre': selected_genre
66 | }
67 |
68 | return render(
69 | request,
70 | template_name='games_by_genre.html',
71 | context=ctx
72 | )
73 |
--------------------------------------------------------------------------------
/locale/pl/LC_MESSAGES/django.po:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the PACKAGE package.
4 | # FIRST AUTHOR , YEAR.
5 | msgid ""
6 | msgstr ""
7 | "Project-Id-Version: PACKAGE VERSION\n"
8 | "Report-Msgid-Bugs-To: \n"
9 | "POT-Creation-Date: 2019-05-19 19:56+0200\n"
10 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
11 | "Last-Translator: FULL NAME \n"
12 | "Language-Team: LANGUAGE \n"
13 | "Language: \n"
14 | "MIME-Version: 1.0\n"
15 | "Content-Type: text/plain; charset=UTF-8\n"
16 | "Content-Transfer-Encoding: 8bit\n"
17 | "Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n"
18 | "%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n"
19 | "%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
20 |
21 | #: developer/forms.py:6 genre/forms.py:6 tag/forms.py:6
22 | msgid "Name"
23 | msgstr "Nazwa"
24 |
25 | #: developer/templates/developer_create.html:7
26 | #: developer/templates/developer_list.html:15
27 | msgid "Create developer"
28 | msgstr "Utwórz developera"
29 |
30 | #: developer/templates/developer_create.html:24
31 | #: game/templates/game_create.html:17 genre/templates/genre_create.html:24
32 | #: tag/templates/tag_create.html:24 tag/templates/tag_list.html:15
33 | #: users/templates/add_user.html:17
34 | msgid "Create"
35 | msgstr "Utwórz"
36 |
37 | #: developer/templates/developer_list.html:8
38 | #: rest_api/templates/rest_api_main.html:16 templates/base.html:107
39 | msgid "Developers"
40 | msgstr "Developerzy"
41 |
42 | #: developer/templates/developer_list.html:41
43 | #: developer/templates/games_by_developer.html:52
44 | #: game/templates/game_details.html:62 game/templates/game_list.html:77
45 | #: genre/templates/games_by_genre.html:53 genre/templates/genre_list.html:40
46 | #: tag/templates/games_by_tag.html:50 tag/templates/tag_list.html:42
47 | #: users/templates/user_list.html:68
48 | msgid "Delete"
49 | msgstr "Usuń"
50 |
51 | #: developer/templates/games_by_developer.html:7
52 | msgid "Games made by: "
53 | msgstr "Gry stworzone przez: "
54 |
55 | #: developer/templates/games_by_developer.html:20
56 | #: game/templates/game_details.html:12 game/templates/game_list.html:37
57 | #: game_recommendation/templates/recommend_by_rated_games_result.html:71
58 | #: game_recommendation/templates/recommend_games_manually_result.html:65
59 | #: genre/templates/games_by_genre.html:20 genre/templates/genre_list.html:7
60 | #: tag/templates/games_by_tag.html:20
61 | msgid "Genre"
62 | msgstr "Gatunek"
63 |
64 | #: developer/templates/games_by_developer.html:29
65 | #: game/templates/game_details.html:19 game/templates/game_list.html:45
66 | #: game/templates/game_list.html:49
67 | #: game_recommendation/templates/recommend_by_rated_games.html:16
68 | #: game_recommendation/templates/recommend_by_rated_games_result.html:88
69 | #: genre/templates/games_by_genre.html:29 tag/templates/games_by_tag.html:27
70 | msgid "No image"
71 | msgstr "Brak zdjęcia"
72 |
73 | #: developer/templates/games_by_developer.html:33
74 | #: game/templates/game_details.html:22 game/templates/game_list.html:54
75 | #: game_recommendation/templates/recommend_by_rated_games_result.html:93
76 | #: game_recommendation/templates/recommend_games_manually_result.html:87
77 | #: genre/templates/games_by_genre.html:33 tag/templates/games_by_tag.html:31
78 | msgid "Developer"
79 | msgstr "Utwórz developera"
80 |
81 | #: developer/templates/games_by_developer.html:38
82 | #: game/templates/game_details.html:27 game/templates/game_list.html:60
83 | #: genre/templates/games_by_genre.html:39
84 | #: rest_api/templates/rest_api_main.html:20 tag/templates/games_by_tag.html:36
85 | #: tag/templates/tag_list.html:7 templates/base.html:111
86 | msgid "Tags"
87 | msgstr "Tagi"
88 |
89 | #: developer/templates/games_by_developer.html:46
90 | #: game/templates/game_details.html:35 game/templates/game_list.html:68
91 | #: game_recommendation/templates/recommend_by_rated_games_result.html:133
92 | #: game_recommendation/templates/recommend_games_manually_result.html:125
93 | #: genre/templates/games_by_genre.html:47 tag/templates/games_by_tag.html:44
94 | msgid "Year"
95 | msgstr "Rok"
96 |
97 | #: developer/views.py:37
98 | msgid "Developer with this name already exists"
99 | msgstr "Developer z taką nazwą już istnieje"
100 |
101 | #: developer/views.py:41
102 | msgid "Developer: {} created successfully"
103 | msgstr "Developer: {} utworzony poprawnie"
104 |
105 | #: developer/views.py:44 game/views.py:58 game_recommendation/views.py:85
106 | #: genre/views.py:44 tag/views.py:41 users/views.py:58 users/views.py:98
107 | msgid "Form invalid"
108 | msgstr "Niepoprawny formularz"
109 |
110 | #: developer/views.py:55
111 | msgid "Developer: {} has been deleted"
112 | msgstr "Developer: {} został utworzony"
113 |
114 | #: final_project_coderslab/settings.py:122
115 | msgid "English"
116 | msgstr "Angielski"
117 |
118 | #: final_project_coderslab/settings.py:123
119 | msgid "Polish"
120 | msgstr "Polski"
121 |
122 | #: game/forms.py:31 game_recommendation/forms.py:23
123 | msgid "Tags (Select 6)"
124 | msgstr "Tagi (Wybierz 6)"
125 |
126 | #: game/forms.py:36
127 | msgid "Should be rated?"
128 | msgstr "Czy ma być oceniana"
129 |
130 | #: game/templates/game_create.html:7 game/templates/game_list.html:14
131 | msgid "Add game"
132 | msgstr "Dodaj grę"
133 |
134 | #: game/templates/game_details.html:39
135 | msgid "Rating"
136 | msgstr "Ocena"
137 |
138 | #: game/templates/game_details.html:44
139 | msgid "No rating"
140 | msgstr "Brak oceny"
141 |
142 | #: game/templates/game_details.html:50
143 | msgid "Rate game"
144 | msgstr "Oceń grę"
145 |
146 | #: game/templates/game_list.html:7 rest_api/templates/rest_api_main.html:14
147 | #: templates/base.html:69
148 | msgid "Games"
149 | msgstr "Gry"
150 |
151 | #: game/templates/game_list.html:73
152 | msgid "Details"
153 | msgstr "Szczegóły"
154 |
155 | #: game/views.py:40
156 | msgid "Game with this name already exists"
157 | msgstr "Developer z taką nazwą już istnieje"
158 |
159 | #: game/views.py:55
160 | msgid "Game: {} created successfully"
161 | msgstr "Developer: {} utworzony poprawnie"
162 |
163 | #: game/views.py:88
164 | msgid "User {} can't rate games"
165 | msgstr "Użytkownik {} nie może oceniać gier"
166 |
167 | #: game_recommendation/templates/recommend_by_rated_games.html:8
168 | msgid "Rate games"
169 | msgstr "Oceń grę"
170 |
171 | #: game_recommendation/templates/recommend_by_rated_games.html:24
172 | msgid "Like"
173 | msgstr "Lubię"
174 |
175 | #: game_recommendation/templates/recommend_by_rated_games.html:28
176 | msgid "Dislike"
177 | msgstr "Nie lubię"
178 |
179 | #: game_recommendation/templates/recommend_by_rated_games_result.html:7
180 | #: game_recommendation/templates/recommend_games_manually_result.html:7
181 | msgid "According to your answers"
182 | msgstr "Zgodnie z Twoimi odpowiedziami"
183 |
184 | #: game_recommendation/templates/recommend_by_rated_games_result.html:10
185 | #: users/templates/user_list.html:53
186 | msgid "Favorite genre"
187 | msgstr "Ulubione gry"
188 |
189 | #: game_recommendation/templates/recommend_by_rated_games_result.html:11
190 | #: game_recommendation/templates/recommend_games_manually_result.html:11
191 | #: users/templates/user_list.html:62
192 | msgid "Favorite developer"
193 | msgstr "Utwórz developera"
194 |
195 | #: game_recommendation/templates/recommend_by_rated_games_result.html:12
196 | #: game_recommendation/templates/recommend_games_manually_result.html:12
197 | #: users/templates/user_list.html:45
198 | msgid "Favorite tags"
199 | msgstr "Ulubione tagi"
200 |
201 | #: game_recommendation/templates/recommend_by_rated_games_result.html:39
202 | #: game_recommendation/templates/recommend_games_manually_result.html:33
203 | msgid "Games you may like"
204 | msgstr "Gry stworzone przez: "
205 |
206 | #: game_recommendation/templates/recommend_by_rated_games_result.html:56
207 | msgid "Score:"
208 | msgstr "Wynik"
209 |
210 | #: game_recommendation/templates/recommend_by_rated_games_result.html:77
211 | #: game_recommendation/templates/recommend_by_rated_games_result.html:101
212 | #: game_recommendation/templates/recommend_games_manually_result.html:72
213 | #: game_recommendation/templates/recommend_games_manually_result.html:94
214 | msgid "Match"
215 | msgstr "Dopasowanie"
216 |
217 | #: game_recommendation/templates/recommend_by_rated_games_result.html:82
218 | #: game_recommendation/templates/recommend_by_rated_games_result.html:106
219 | #: game_recommendation/templates/recommend_games_manually_result.html:77
220 | #: game_recommendation/templates/recommend_games_manually_result.html:99
221 | msgid "No match"
222 | msgstr "Brak dopasowania"
223 |
224 | #: game_recommendation/templates/recommend_by_rated_games_result.html:113
225 | #: game_recommendation/templates/recommend_games_manually_result.html:107
226 | msgid "Matching tags"
227 | msgstr "Pasujące tagi"
228 |
229 | #: game_recommendation/templates/recommend_by_rated_games_result.html:123
230 | #: game_recommendation/templates/recommend_games_manually_result.html:116
231 | msgid "Other tags"
232 | msgstr "Pozostałe tagi"
233 |
234 | #: game_recommendation/templates/recommend_games_manually.html:7
235 | msgid "Choose your preferences"
236 | msgstr "Wybierz ulubione"
237 |
238 | #: game_recommendation/templates/recommend_games_manually.html:17
239 | msgid "Submit"
240 | msgstr "Zatwierdź"
241 |
242 | #: game_recommendation/templates/recommend_games_manually_result.html:10
243 | msgid "Favorite game"
244 | msgstr "Ulubiona gra"
245 |
246 | #: game_recommendation/templates/recommend_games_manually_result.html:50
247 | msgid "Score"
248 | msgstr "Wynik"
249 |
250 | #: genre/templates/games_by_genre.html:7
251 | msgid "Games with genre: "
252 | msgstr "Gry z gatunku: "
253 |
254 | #: genre/templates/genre_create.html:7 genre/templates/genre_list.html:14
255 | msgid "Create genre"
256 | msgstr "Utwórz"
257 |
258 | #: genre/views.py:37
259 | msgid "Genre with this name already exists"
260 | msgstr "Developer z taką nazwą już istnieje"
261 |
262 | #: genre/views.py:41
263 | msgid "Genre: {} created successfully"
264 | msgstr "Developer: {} utworzony poprawnie"
265 |
266 | #: news_api/templates/news_main.html:8
267 | msgid "Gaming an Tech news"
268 | msgstr "Wiadomości gamingowe i technologiczne"
269 |
270 | #: news_api/templates/news_main.html:46
271 | msgid "Read news"
272 | msgstr "Czytaj"
273 |
274 | #: rest_api/templates/rest_api_main.html:7
275 | msgid "Select API"
276 | msgstr "Rest API"
277 |
278 | #: rest_api/templates/rest_api_main.html:12 templates/base.html:85
279 | msgid "Users"
280 | msgstr "Użytkownicy"
281 |
282 | #: rest_api/templates/rest_api_main.html:18 templates/base.html:109
283 | msgid "Genres"
284 | msgstr "Gatunek"
285 |
286 | #: tag/templates/games_by_tag.html:7
287 | msgid "Games with tag: "
288 | msgstr "Gry z tagiem: "
289 |
290 | #: tag/templates/tag_create.html:7
291 | msgid "Create tag"
292 | msgstr "Utwórz"
293 |
294 | #: tag/views.py:34
295 | msgid "Tag with this name already exists"
296 | msgstr "Developer z taką nazwą już istnieje"
297 |
298 | #: tag/views.py:38
299 | msgid "Tag: {} created successfully"
300 | msgstr "Developer: {} utworzony poprawnie"
301 |
302 | #: templates/base.html:6 templates/base.html:155
303 | msgid "Gameweb"
304 | msgstr "Gameweb"
305 |
306 | #: templates/base.html:31
307 | msgid "News"
308 | msgstr "Wiadomości"
309 |
310 | #: templates/base.html:51
311 | msgid "Recommend Game"
312 | msgstr "Poleć grę"
313 |
314 | #: templates/base.html:56
315 | msgid "Recommend by tags"
316 | msgstr "Poleć na podstawie tagów"
317 |
318 | #: templates/base.html:59
319 | msgid "Recommend by rated games"
320 | msgstr "Poleć na podstawie ocenionych"
321 |
322 | #: templates/base.html:73
323 | msgid "Add games"
324 | msgstr "Dodaj grę"
325 |
326 | #: templates/base.html:75
327 | msgid "Show games"
328 | msgstr "Pokaż gry"
329 |
330 | #: templates/base.html:89
331 | msgid "Add users"
332 | msgstr "Dodaj użytkownika"
333 |
334 | #: templates/base.html:91
335 | msgid "Show users"
336 | msgstr "Pokaż użytkowników"
337 |
338 | #: templates/base.html:101
339 | msgid "Other"
340 | msgstr "Inne"
341 |
342 | #: templates/base.html:105
343 | msgid "Rest API"
344 | msgstr "Rest API"
345 |
346 | #: templates/base.html:113
347 | msgid "About"
348 | msgstr "O stronie"
349 |
350 | #: templates/base.html:126
351 | msgid "Logout"
352 | msgstr "Wyloguj"
353 |
354 | #: templates/base.html:135
355 | msgid "Register"
356 | msgstr "Zarejestruj"
357 |
358 | #: templates/base.html:139 users/templates/login.html:7
359 | #: users/templates/login.html:17
360 | msgid "Login"
361 | msgstr "Zaloguj"
362 |
363 | #: users/templates/add_user.html:7 users/templates/user_list.html:14
364 | msgid "Create user"
365 | msgstr "Utwórz"
366 |
367 | #: users/templates/user_list.html:7
368 | msgid "User list"
369 | msgstr "Lista użytkowników"
370 |
371 | #: users/templates/user_list.html:37
372 | msgid "Full name"
373 | msgstr "Pełna nazwa"
374 |
375 | #: users/templates/user_list.html:41
376 | msgid "Email"
377 | msgstr "Email"
378 |
379 | #: users/views.py:43
380 | msgid "User with this name already exists"
381 | msgstr "Developer z taką nazwą już istnieje"
382 |
383 | #: users/views.py:55
384 | msgid "User: {} created successfully"
385 | msgstr "Developer: {} utworzony poprawnie"
386 |
387 | #: users/views.py:95
388 | msgid "Wrong password"
389 | msgstr "Błędne hasło"
390 |
--------------------------------------------------------------------------------
/main_app/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dawidbudzynski/gameweb_python_django/b95719f04b43d2cd0c804e5972a43648e05d9e39/main_app/__init__.py
--------------------------------------------------------------------------------
/main_app/admin.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dawidbudzynski/gameweb_python_django/b95719f04b43d2cd0c804e5972a43648e05d9e39/main_app/admin.py
--------------------------------------------------------------------------------
/main_app/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class MainAppConfig(AppConfig):
5 | name = 'main_app'
6 |
--------------------------------------------------------------------------------
/main_app/context_processor.py:
--------------------------------------------------------------------------------
1 | import csv
2 | import os
3 |
4 | from django.conf import settings
5 |
6 |
7 | def gaming_quotes_processor(request):
8 | all_quotes = []
9 | path = os.path.join(settings.MEDIA_ROOT, 'csv/gaming_quotes.csv')
10 | with open(path) as f:
11 | for row in csv.reader(f, delimiter=';'):
12 | all_quotes.append({'author': row[0], 'quote': row[1]})
13 | return {'all_quotes': all_quotes}
14 |
--------------------------------------------------------------------------------
/main_app/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dawidbudzynski/gameweb_python_django/b95719f04b43d2cd0c804e5972a43648e05d9e39/main_app/migrations/__init__.py
--------------------------------------------------------------------------------
/main_app/models.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dawidbudzynski/gameweb_python_django/b95719f04b43d2cd0c804e5972a43648e05d9e39/main_app/models.py
--------------------------------------------------------------------------------
/main_app/tasks.py:
--------------------------------------------------------------------------------
1 | import csv
2 | import os
3 | from datetime import datetime
4 |
5 | from celery.decorators import periodic_task, task
6 | from celery.task.schedules import crontab
7 | from celery.utils.log import get_task_logger
8 | from django.conf import settings
9 |
10 | from game.models import Game
11 |
12 | logger = get_task_logger(__name__)
13 |
14 |
15 | def save_games_to_csv(filename):
16 | all_games = Game.objects.all()
17 | with open(os.path.join(settings.MEDIA_ROOT, 'csv', filename), 'w') as filepath:
18 | writer = csv.DictWriter(
19 | filepath,
20 | fieldnames=['time', 'title', 'developer', 'genre', 'year'],
21 | delimiter=';'
22 | )
23 | writer.writeheader()
24 | for game in all_games:
25 | writer.writerow({
26 | 'time': datetime.now(),
27 | 'title': game.title,
28 | 'developer': game.developer.name,
29 | 'genre': game.genre.name,
30 | 'year': game.year
31 | })
32 | logger.info("Games downloaded to CSV successfully")
33 |
34 |
35 | @task(name="save_games_to_csv_task")
36 | def save_games_to_csv_task():
37 | """Saves all games to CSV file in background"""
38 | save_games_to_csv('game_data.csv')
39 |
40 |
41 | @periodic_task(
42 | run_every=crontab(minute=0, hour=0),
43 | name="save_games_to_csv_backup",
44 | ignore_result=True
45 | )
46 | def save_games_backup():
47 | """Saves all games to CSV file everyday at midnight"""
48 | save_games_to_csv('game_data_backup.csv')
49 |
--------------------------------------------------------------------------------
/main_app/templates/403.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load bootstrap4 i18n staticfiles %}
3 | {% block content %}
4 |
5 |
6 |
7 | {% trans "Sorry. You can't do that" %} |
8 |
9 |
10 |
11 | {% endblock %}
--------------------------------------------------------------------------------
/main_app/templates/about.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load bootstrap4 i18n staticfiles %}
3 | {% block content %}
4 |
5 |
6 |
7 |
8 | {% trans 'Loga made with:' %}
9 | DesignEvo
11 |
12 | |
13 |
14 |
15 |
16 |
20 | |
21 |
22 |
23 |
24 | {% endblock %}
--------------------------------------------------------------------------------
/main_app/templates/download_games_to_csv_result.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load bootstrap4 i18n staticfiles %}
3 | {% block content %}
4 | {% include 'message_block.html' %}
5 | {% endblock %}
--------------------------------------------------------------------------------
/main_app/tests.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dawidbudzynski/gameweb_python_django/b95719f04b43d2cd0c804e5972a43648e05d9e39/main_app/tests.py
--------------------------------------------------------------------------------
/main_app/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import path
2 |
3 | from . import views
4 |
5 | app_name = 'main_app'
6 |
7 | urlpatterns = [
8 | path('about/', views.AboutView.as_view(),
9 | name='about'),
10 | path('download_to_csv/', views.DownloadGamesToCSVView.as_view(),
11 | name='download-to-csv')
12 | ]
13 |
--------------------------------------------------------------------------------
/main_app/views.py:
--------------------------------------------------------------------------------
1 | from django.contrib import messages
2 | from django.shortcuts import render
3 | from django.utils.translation import ugettext as _
4 | from django.views import View
5 | from django.views.generic import TemplateView
6 |
7 | from .tasks import save_games_to_csv_task
8 |
9 |
10 | class AboutView(TemplateView):
11 | template_name = 'about.html'
12 |
13 |
14 | class DownloadGamesToCSVView(View):
15 |
16 | def get(self, request):
17 | save_games_to_csv_task.delay()
18 | messages.add_message(request, messages.INFO, _(
19 | 'Games will be downloaded in background. Come back in few moments'
20 | ))
21 | return render(
22 | request,
23 | template_name='download_games_to_csv_result.html'
24 | )
25 |
--------------------------------------------------------------------------------
/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import os
3 | import sys
4 |
5 | if __name__ == "__main__":
6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "final_project_coderslab.settings")
7 | try:
8 | from django.core.management import execute_from_command_line
9 | except ImportError as exc:
10 | raise ImportError(
11 | "Couldn't import Django. Are you sure it's installed and "
12 | "available on your PYTHONPATH environment variable? Did you "
13 | "forget to activate a virtual environment?"
14 | ) from exc
15 | execute_from_command_line(sys.argv)
16 |
--------------------------------------------------------------------------------
/media/csv/game_data.csv:
--------------------------------------------------------------------------------
1 | time;title;developer;genre;year
2 |
--------------------------------------------------------------------------------
/media/csv/gaming_quotes.csv:
--------------------------------------------------------------------------------
1 | Paarthurnax, Elder Scrolls V: Skyrim;What is better? To be born good or to overcome your evil nature through great effort?
2 | Javik, Mass Effect 3;Stand in the ashes of a trillion dead souls, and asks the ghosts if honor matters. The silence is your answer.
3 | Khan, Metro 2033;Even in dark times, we cannot relinquish the things that make us human.
--------------------------------------------------------------------------------
/news_api/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dawidbudzynski/gameweb_python_django/b95719f04b43d2cd0c804e5972a43648e05d9e39/news_api/__init__.py
--------------------------------------------------------------------------------
/news_api/admin.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dawidbudzynski/gameweb_python_django/b95719f04b43d2cd0c804e5972a43648e05d9e39/news_api/admin.py
--------------------------------------------------------------------------------
/news_api/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class NewsApiConfig(AppConfig):
5 | name = 'news_api'
6 |
--------------------------------------------------------------------------------
/news_api/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dawidbudzynski/gameweb_python_django/b95719f04b43d2cd0c804e5972a43648e05d9e39/news_api/migrations/__init__.py
--------------------------------------------------------------------------------
/news_api/models.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dawidbudzynski/gameweb_python_django/b95719f04b43d2cd0c804e5972a43648e05d9e39/news_api/models.py
--------------------------------------------------------------------------------
/news_api/templates/news_main.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load bootstrap4 i18n staticfiles %}
3 | {% block content %}
4 |
31 | {% for article in articles %}
32 |
33 |
34 | {{ article.title }} |
35 |
36 |
38 | |
39 |
40 |
41 | {{ article.description }} |
42 |
43 |
44 |
45 | {% trans 'Read news' %}
47 | |
48 |
49 |
50 | {% endfor %}
51 | {% endblock %}
52 |
--------------------------------------------------------------------------------
/news_api/tests.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from django.test import TestCase
4 | from django.urls import reverse
5 |
6 |
7 | class NewsAPITests(TestCase, unittest.TestCase):
8 |
9 | def test_game_form(self):
10 | response = self.client.get(reverse('news_api:show-news', kwargs={'news_source': 'polygon'}))
11 | self.assertEquals(response.status_code, 200)
12 | self.assertTemplateUsed(response, 'news_main.html')
13 |
--------------------------------------------------------------------------------
/news_api/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import path
2 |
3 | from . import views
4 |
5 | app_name = 'news_api'
6 |
7 | urlpatterns = [
8 | path('', views.TechNewsView.as_view(),
9 | name='mainpage'),
10 | path('show_news/', views.TechNewsView.as_view(),
11 | name='show-news'),
12 | ]
13 |
--------------------------------------------------------------------------------
/news_api/views.py:
--------------------------------------------------------------------------------
1 | import requests
2 | from decouple import config
3 | from django.shortcuts import render
4 | from django.views import View
5 |
6 | from constants import NEWS_SOURCE_DATA_ALL
7 |
8 | NEWS_API_KEY = config('NEWS_API_KEY', cast=str)
9 |
10 |
11 | class TechNewsView(View):
12 | """Display gaming and tech news using API"""
13 |
14 | def get(self, request, news_source='polygon'):
15 | image_url = None
16 | source_name = None
17 | selected_source = None
18 | for news_source_key, news_source_values in NEWS_SOURCE_DATA_ALL.items():
19 | if news_source == news_source_key:
20 | selected_source = news_source_key
21 | image_url = news_source_values['image_url']
22 | source_name = news_source_values['api_name']
23 | url = (
24 | 'https://newsapi.org/v2/top-headlines?sources={}&apiKey={}'.format(
25 | selected_source, NEWS_API_KEY)
26 | )
27 | response = requests.get(url)
28 | ctx = {
29 | 'articles': response.json()['articles'],
30 | 'image_url': image_url,
31 | 'source_name': source_name
32 | }
33 | return render(
34 | request,
35 | template_name='news_main.html',
36 | context=ctx
37 | )
38 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | amqp==2.4.2
2 | atomicwrites==1.3.0
3 | attrs==19.1.0
4 | backcall==0.1.0
5 | billiard==3.6.0.0
6 | boto3==1.9.154
7 | botocore==1.12.154
8 | celery==4.3.0
9 | certifi==2018.4.16
10 | chardet==3.0.4
11 | coverage==4.5.3
12 | decorator==4.4.0
13 | dj-database-url==0.5.0
14 | Django==2.1.11
15 | django-bootstrap4==0.0.8
16 | django-debug-toolbar==1.11
17 | django-filter==2.1.0
18 | django-heroku==0.3.1
19 | django-storages==1.7.1
20 | django-timezone-field==3.0
21 | djangorestframework==3.9.4
22 | docutils==0.14
23 | ez-setup==0.9
24 | flake8==3.6.0
25 | gunicorn==19.9.0
26 | idna==2.6
27 | ipdb==0.12
28 | ipython==7.5.0
29 | ipython-genutils==0.2.0
30 | jedi==0.13.3
31 | jmespath==0.9.4
32 | kombu==4.5.0
33 | Markdown==3.1
34 | mccabe==0.6.1
35 | more-itertools==7.0.0
36 | parso==0.4.0
37 | pexpect==4.7.0
38 | pickleshare==0.7.5
39 | Pillow==6.2.0
40 | pluggy==0.11.0
41 | prompt-toolkit==2.0.9
42 | psycopg2==2.7.5
43 | psycopg2-binary==2.8.2
44 | ptyprocess==0.6.0
45 | py==1.8.0
46 | pycodestyle==2.4.0
47 | pyflakes==2.0.0
48 | Pygments==2.4.0
49 | pytest==4.5.0
50 | python-crontab==2.3.6
51 | python-dateutil==2.8.0
52 | python-decouple==3.1
53 | pytz==2018.4
54 | redis==3.2.1
55 | requests==2.18.4
56 | s3transfer==0.2.0
57 | six==1.12.0
58 | sqlparse==0.3.0
59 | traitlets==4.3.2
60 | urllib3==1.22
61 | vine==1.3.0
62 | wcwidth==0.1.7
63 | whitenoise==4.1
64 |
--------------------------------------------------------------------------------
/rest_api/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dawidbudzynski/gameweb_python_django/b95719f04b43d2cd0c804e5972a43648e05d9e39/rest_api/__init__.py
--------------------------------------------------------------------------------
/rest_api/admin.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dawidbudzynski/gameweb_python_django/b95719f04b43d2cd0c804e5972a43648e05d9e39/rest_api/admin.py
--------------------------------------------------------------------------------
/rest_api/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class RestApiConfig(AppConfig):
5 | name = 'rest_api'
6 |
--------------------------------------------------------------------------------
/rest_api/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dawidbudzynski/gameweb_python_django/b95719f04b43d2cd0c804e5972a43648e05d9e39/rest_api/migrations/__init__.py
--------------------------------------------------------------------------------
/rest_api/models.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dawidbudzynski/gameweb_python_django/b95719f04b43d2cd0c804e5972a43648e05d9e39/rest_api/models.py
--------------------------------------------------------------------------------
/rest_api/serializers.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth.models import User
2 |
3 | from rest_framework import serializers
4 |
5 | from developer.models import Developer
6 | from genre.models import Genre
7 | from tag.models import Tag
8 | from game.models import Game
9 |
10 |
11 | class UserSerializer(serializers.HyperlinkedModelSerializer):
12 | url = serializers.HyperlinkedIdentityField(view_name="rest_api:users-detail")
13 |
14 | class Meta:
15 | model = User
16 | fields = ('url', 'username', 'email')
17 |
18 |
19 | class DeveloperSerializer(serializers.HyperlinkedModelSerializer):
20 | url = serializers.HyperlinkedIdentityField(view_name="rest_api:developers-detail")
21 |
22 | class Meta:
23 | model = Developer
24 | fields = ('url', 'name')
25 |
26 |
27 | class GenreSerializer(serializers.HyperlinkedModelSerializer):
28 | url = serializers.HyperlinkedIdentityField(view_name="rest_api:genres-detail")
29 |
30 | class Meta:
31 | model = Genre
32 | fields = ('url', 'name')
33 |
34 |
35 | class TagSerializer(serializers.HyperlinkedModelSerializer):
36 | url = serializers.HyperlinkedIdentityField(view_name="rest_api:tags-detail")
37 |
38 | class Meta:
39 | model = Tag
40 | fields = ('url', 'name')
41 |
42 |
43 | class GameSerializer(serializers.HyperlinkedModelSerializer):
44 | url = serializers.HyperlinkedIdentityField(view_name="rest_api:games-detail")
45 | developer = serializers.HyperlinkedRelatedField(
46 | many=False,
47 | read_only=True,
48 | view_name='rest_api:developers-detail'
49 | )
50 | genre = serializers.HyperlinkedRelatedField(
51 | many=False,
52 | read_only=True,
53 | view_name='rest_api:genres-detail'
54 | )
55 | tags = serializers.HyperlinkedRelatedField(
56 | many=True,
57 | read_only=True,
58 | view_name='rest_api:tags-detail'
59 | )
60 |
61 | class Meta:
62 | model = Game
63 | fields = ('url', 'title', 'year', 'developer', 'genre', 'tags', 'image', 'to_be_rated')
64 |
--------------------------------------------------------------------------------
/rest_api/templates/rest_api_main.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load bootstrap4 i18n staticfiles %}
3 |
4 | {% block content %}
5 |
6 |
7 |
{% trans 'Select API' %}
8 |
9 |
23 |
24 | {% endblock %}
--------------------------------------------------------------------------------
/rest_api/tests.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dawidbudzynski/gameweb_python_django/b95719f04b43d2cd0c804e5972a43648e05d9e39/rest_api/tests.py
--------------------------------------------------------------------------------
/rest_api/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls import include, url
2 | from rest_framework import routers
3 |
4 | from . import views
5 |
6 | app_name = 'rest_api'
7 |
8 | router = routers.DefaultRouter()
9 | router.register(r'users', views.UserViewSet, 'users')
10 | router.register(r'games', views.GameViewSet, 'games')
11 | router.register(r'developers', views.DeveloperViewSet, 'developers')
12 | router.register(r'genres', views.GenreViewSet, 'genres')
13 | router.register(r'tags', views.TagViewSet, 'tags')
14 |
15 | urlpatterns = [
16 | url(r'^', include(router.urls)),
17 | url(r'^api-main/', views.RestApiMain.as_view(), name='api-main'),
18 | url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
19 | ]
20 |
--------------------------------------------------------------------------------
/rest_api/views.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth.models import User
2 | from django.views.generic import TemplateView
3 | from rest_framework import viewsets
4 |
5 | from developer.models import Developer
6 | from game.models import Game
7 | from genre.models import Genre
8 | from tag.models import Tag
9 | from .serializers import DeveloperSerializer, GameSerializer, GenreSerializer, TagSerializer, UserSerializer
10 |
11 |
12 | class RestApiMain(TemplateView):
13 | template_name = 'rest_api_main.html'
14 |
15 |
16 | class UserViewSet(viewsets.ModelViewSet):
17 | """
18 | API endpoint that allows user objects to be viewed or edited.
19 | """
20 | queryset = User.objects.all().order_by('-date_joined')
21 | serializer_class = UserSerializer
22 |
23 |
24 | class DeveloperViewSet(viewsets.ModelViewSet):
25 | """
26 | API endpoint that allows developer objects to be viewed or edited.
27 | """
28 | queryset = Developer.objects.all()
29 | serializer_class = DeveloperSerializer
30 |
31 |
32 | class GenreViewSet(viewsets.ModelViewSet):
33 | """
34 | API endpoint that allows genre objects to be viewed or edited.
35 | """
36 | queryset = Genre.objects.all()
37 | serializer_class = GenreSerializer
38 |
39 |
40 | class TagViewSet(viewsets.ModelViewSet):
41 | """
42 | API endpoint that allows tag objects to be viewed or edited.
43 | """
44 | queryset = Tag.objects.all()
45 | serializer_class = TagSerializer
46 |
47 |
48 | class GameViewSet(viewsets.ModelViewSet):
49 | """
50 | API endpoint that allows game objects to be viewed or edited.
51 | """
52 | queryset = Game.objects.all()
53 | serializer_class = GameSerializer
54 |
--------------------------------------------------------------------------------
/runtime.txt:
--------------------------------------------------------------------------------
1 | python-3.6.8
2 |
--------------------------------------------------------------------------------
/settings.ini.example:
--------------------------------------------------------------------------------
1 | [settings]
2 | DEBUG = True
3 | WORK_ON_POSTGRE_SQL = False
4 |
5 | SECRET_KEY =
6 |
7 | # NEWS API
8 | NEWS_API_KEY =
9 |
10 | # DATABASE CONFIG
11 | DB_NAME =
12 | DB_USER =
13 | DB_PASSWORD =
14 | DB_HOST =
15 | DB_PORT =
16 |
17 | # REDIS CONFIG
18 | BROKER_URL =
19 | CELERY_RESULT_BACKEND =
20 |
21 | # AWS CONFIG
22 | AWS_ACCESS_KEY_ID =
23 | AWS_SECRET_ACCESS_KEY =
24 | AWS_STORAGE_BUCKET_NAME =
25 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [flake8]
2 | exclude = */docs/*,*/.tox/*,*/.venv/*,*/migrations/*
3 | max-line-length = 119
4 |
--------------------------------------------------------------------------------
/static/css/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin-top: 70px;
3 | font-family: 'Righteous', serif;
4 | background-color: whitesmoke;
5 | }
6 |
7 | .title {
8 | font-family: 'Faster One', serif;
9 | color: mintcream;
10 | padding: 20px;
11 | text-align: center;
12 | }
13 |
14 | div.textContent {
15 | display: none;
16 | }
17 |
18 | .header {
19 | background-color: #005ec3;
20 | height: 140px;
21 | }
22 |
23 | .content {
24 | margin-left: auto;
25 | margin-right: auto;
26 | margin-top: 10px;
27 | width: 80%;
28 | }
29 |
30 | a:hover {
31 | color: mintcream;
32 | text-decoration: none;
33 | }
34 |
35 | th, h4 {
36 | color: #2B2C5F;
37 | margin-left: 100px;
38 | margin-right: auto;
39 | }
40 |
41 | h1, h2, h3, h5, h6, p, a, label {
42 | color: #2B2C5F;
43 | text-align: center;
44 | }
45 |
46 | a:hover {
47 | color: darkblue;
48 | }
49 |
50 | .table {
51 | border-collapse: separate !important;
52 | background-color: #E4E5EE;
53 | border: 3px solid #005ec3;
54 | border-radius: 15px;
55 | }
56 |
57 | .table td {
58 | vertical-align: middle;
59 | }
60 |
61 | .navbar {
62 | background-color: #005ec3;
63 | height: 50px;
64 | }
65 |
66 | .navbar.navbar-expand-lg.navbar-dark li a {
67 | color: mintcream;
68 | font-size: 16px;
69 | }
70 |
71 | .navbar-nav > li > .dropdown-menu {
72 | background-color: #005ec3;
73 | }
74 |
75 | .navbar-nav > li > .dropdown-menu > a {
76 | border: transparent solid 2px;
77 | }
78 |
79 | .navbar-nav > li > .dropdown-menu > a:hover {
80 | background-color: #005ec3;
81 | border: white solid 2px;
82 | }
83 |
84 | .dropdown-toggle::after {
85 | display: none
86 | }
87 |
88 | .table > tbody > tr > td,
89 | .table > tbody > tr > th,
90 | .table > thead > tr > td,
91 | .table > thead > tr > th {
92 | border: none;
93 | }
94 |
95 | input[type="checkbox"] {
96 | width: 50px;
97 | height: 30px;
98 | cursor: pointer;
99 | }
100 |
101 | .checkboxmultiple {
102 | border-radius: 15px;
103 | padding: 20px 100px 20px 100px;
104 | list-style-type: none;
105 | margin: auto;
106 | }
107 |
108 | .checkboxmultiple li {
109 | color: black;
110 | font-weight: bold;
111 | }
112 |
113 | .btn-primary {
114 | width: 250px;
115 | margin-left: auto;
116 | margin-right: auto;
117 | }
118 |
119 | .btn-secondary {
120 | background-color: #005ec3;
121 | width: 250px;
122 | margin-left: auto;
123 | margin-right: auto;
124 | }
125 |
126 | .btn-secondary:hover {
127 | background-color: #420EE8;
128 | }
129 |
130 | .btn-success {
131 | align-self: center;
132 | align-items: center;
133 | align-content: center;
134 | width: 250px;
135 | margin-left: auto;
136 | margin-right: auto;
137 | }
138 |
139 | .btn-dark {
140 | background-color: #005ec3;
141 | }
142 |
143 | .btn-dark:hover {
144 | background-color: #420EE8;
145 | }
146 |
147 | .btn-group-justified > .btn-group:nth-of-type(odd) {
148 | width: .25%;
149 | }
150 |
151 | .btn-danger {
152 | width: 200px;
153 | border: none;
154 | }
155 |
156 | .btn-danger > a {
157 | color: mintcream;
158 | }
159 |
160 | input[type=text] {
161 | padding: 5px;
162 | border: 2px solid #ccc;
163 | -webkit-border-radius: 5px;
164 | border-radius: 5px;
165 | width: 100%;
166 | }
167 |
168 | input[type=password] {
169 | padding: 5px;
170 | border: 2px solid #ccc;
171 | -webkit-border-radius: 5px;
172 | border-radius: 5px;
173 | width: 100%;
174 | }
175 |
176 | input[type=email] {
177 | padding: 5px;
178 | border: 2px solid #ccc;
179 | -webkit-border-radius: 5px;
180 | border-radius: 5px;
181 | width: 100%;
182 | }
183 |
184 | select {
185 | padding: 5px;
186 | border: 2px solid #ccc;
187 | -webkit-border-radius: 5px;
188 | border-radius: 5px;
189 | width: 100%;
190 | }
191 |
192 | textarea {
193 | padding: 5px;
194 | border: 2px solid #ccc;
195 | -webkit-border-radius: 5px;
196 | border-radius: 5px;
197 | width: 100%;
198 | height: 50px;
199 | }
200 |
201 | input[type=number] {
202 | padding: 5px;
203 | border: 2px solid #ccc;
204 | -webkit-border-radius: 5px;
205 | border-radius: 5px;
206 | width: 100%;
207 | }
208 |
209 | input[type=file] {
210 | padding: 5px;
211 | border: 2px solid #ccc;
212 | -webkit-border-radius: 5px;
213 | border-radius: 5px;
214 | width: 100%;
215 | }
216 |
217 | input[type=number]:focus {
218 | border-color: #333;
219 | }
220 |
221 | input[type=text]:focus {
222 | border-color: #333;
223 | }
224 |
225 | input[type=checkbox].css-checkbox {
226 | position: absolute;
227 | z-index: -1000;
228 | left: -1000px;
229 | overflow: hidden;
230 | clip: rect(0 0 0 0);
231 | height: 1px;
232 | width: 1px;
233 | margin: -1px;
234 | padding: 0;
235 | border: 0;
236 | }
237 |
238 | .container {
239 | display: block;
240 | position: relative;
241 | padding-left: 35px;
242 | cursor: pointer;
243 | font-size: 18px;
244 | -webkit-user-select: none;
245 | -moz-user-select: none;
246 | -ms-user-select: none;
247 | user-select: none;
248 | }
249 |
250 | .radio_buttons_container {
251 | background-color: #6B6DD8;
252 | border: 3px solid #005ec3;
253 | border-radius: 5px;
254 | padding: 5px;
255 | margin-right: 150px;
256 | }
257 |
258 | /* Hide the browser's default radio button */
259 | .container input {
260 | position: absolute;
261 | opacity: 0;
262 | cursor: pointer;
263 | }
264 |
265 | /* Create a custom radio button */
266 | .checkmark {
267 | position: absolute;
268 | top: 0;
269 | left: 0;
270 | height: 25px;
271 | width: 25px;
272 | background-color: whitesmoke;
273 | border-radius: 50%;
274 | }
275 |
276 | /* On mouse-over, add a grey background color */
277 | .container:hover input ~ .checkmark {
278 | background-color: #ccc;
279 | }
280 |
281 | /* When the radio button is checked, add a blue background */
282 | .container input:checked ~ .checkmark {
283 | background-color: #2196F3;
284 | }
285 |
286 | /* Create the indicator (the dot/circle - hidden when not checked) */
287 | .checkmark:after {
288 | content: "";
289 | position: absolute;
290 | display: none;
291 | }
292 |
293 | /* Show the indicator (dot/circle) when checked */
294 | .container input:checked ~ .checkmark:after {
295 | display: block;
296 | }
297 |
298 | /* Style the indicator (dot/circle) */
299 | .container .checkmark:after {
300 | top: 9px;
301 | left: 9px;
302 | width: 8px;
303 | height: 8px;
304 | border-radius: 50%;
305 | background: white;
306 | }
307 |
308 | .news_button {
309 | width: 22%;
310 | }
311 |
312 | .header-title{
313 | color: mintcream;
314 | font-size: 30px;
315 | margin-top: -10px;
316 | }
317 |
318 | #header-logo{
319 | height: 140px;
320 | width: auto;
321 | float: left;
322 | }
323 |
324 | #quote-text{
325 | font-size: 18px;
326 | color: mintcream;
327 | }
328 |
329 | #image-banner{
330 | height: 180px;
331 | width: 100%;
332 | }
333 |
334 | #news-source-logo{
335 | width: 150px;
336 | height: 150px;
337 | border-radius: 5px;
338 | margin-left: 70px;
339 | }
340 |
341 | .news-header-text{
342 | margin-right: 300px;
343 | }
344 |
--------------------------------------------------------------------------------
/static/img/IGN.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dawidbudzynski/gameweb_python_django/b95719f04b43d2cd0c804e5972a43648e05d9e39/static/img/IGN.jpg
--------------------------------------------------------------------------------
/static/img/icon.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dawidbudzynski/gameweb_python_django/b95719f04b43d2cd0c804e5972a43648e05d9e39/static/img/icon.jpg
--------------------------------------------------------------------------------
/static/img/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dawidbudzynski/gameweb_python_django/b95719f04b43d2cd0c804e5972a43648e05d9e39/static/img/icon.png
--------------------------------------------------------------------------------
/static/img/ign.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dawidbudzynski/gameweb_python_django/b95719f04b43d2cd0c804e5972a43648e05d9e39/static/img/ign.png
--------------------------------------------------------------------------------
/static/img/logo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dawidbudzynski/gameweb_python_django/b95719f04b43d2cd0c804e5972a43648e05d9e39/static/img/logo.jpg
--------------------------------------------------------------------------------
/static/img/new_banner.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dawidbudzynski/gameweb_python_django/b95719f04b43d2cd0c804e5972a43648e05d9e39/static/img/new_banner.jpg
--------------------------------------------------------------------------------
/static/img/polygon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dawidbudzynski/gameweb_python_django/b95719f04b43d2cd0c804e5972a43648e05d9e39/static/img/polygon.png
--------------------------------------------------------------------------------
/static/img/techradar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dawidbudzynski/gameweb_python_django/b95719f04b43d2cd0c804e5972a43648e05d9e39/static/img/techradar.png
--------------------------------------------------------------------------------
/static/img/verge.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dawidbudzynski/gameweb_python_django/b95719f04b43d2cd0c804e5972a43648e05d9e39/static/img/verge.png
--------------------------------------------------------------------------------
/static/js/dropdown_hover.js:
--------------------------------------------------------------------------------
1 | $('.navbar .dropdown').hover(function() {
2 | $(this).find('.dropdown-menu').first().stop(true, true).delay(250).slideDown();
3 | }, function() {
4 | $(this).find('.dropdown-menu').first().stop(true, true).delay(100).slideUp()
5 | });
--------------------------------------------------------------------------------
/static/js/main.js:
--------------------------------------------------------------------------------
1 | let limit = 7;
2 | $('input[name="tags"]').on('change', function (evt) {
3 | if ($('input[name="tags"]:checked').length >= limit) {
4 | this.checked = false;
5 | }
6 | });
7 |
8 |
--------------------------------------------------------------------------------
/static/js/slider.js:
--------------------------------------------------------------------------------
1 | let cnt = 0,
2 | texts = [];
3 |
4 | $(".textContent").each(function () {
5 | texts[cnt++] = $(this).html();
6 | });
7 |
8 | function slide() {
9 | if (cnt >= texts.length) cnt = 0;
10 | $('#textMessage')
11 | .html(texts[cnt++])
12 | .fadeIn(3000).delay(3000)
13 | .fadeOut(3000, slide);
14 | }
15 |
16 | slide();
--------------------------------------------------------------------------------
/tag/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dawidbudzynski/gameweb_python_django/b95719f04b43d2cd0c804e5972a43648e05d9e39/tag/__init__.py
--------------------------------------------------------------------------------
/tag/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 |
3 | from .models import Tag
4 |
5 | admin.site.register(Tag)
6 |
--------------------------------------------------------------------------------
/tag/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class TagConfig(AppConfig):
5 | name = 'tag'
6 |
--------------------------------------------------------------------------------
/tag/forms.py:
--------------------------------------------------------------------------------
1 | from django.forms import (CharField, Form)
2 | from django.utils.translation import ugettext_lazy as _
3 |
4 |
5 | class AddTagForm(Form):
6 | name = CharField(max_length=64, label=_('Name'))
7 |
--------------------------------------------------------------------------------
/tag/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.1.2 on 2019-05-18 10:16
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | initial = True
9 |
10 | dependencies = [
11 | ]
12 |
13 | operations = [
14 | migrations.CreateModel(
15 | name='Tag',
16 | fields=[
17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18 | ('name', models.CharField(max_length=64)),
19 | ],
20 | ),
21 | ]
22 |
--------------------------------------------------------------------------------
/tag/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dawidbudzynski/gameweb_python_django/b95719f04b43d2cd0c804e5972a43648e05d9e39/tag/migrations/__init__.py
--------------------------------------------------------------------------------
/tag/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
3 |
4 | class Tag(models.Model):
5 | name = models.CharField(max_length=64)
6 |
7 | def __str__(self):
8 | return self.name
9 |
--------------------------------------------------------------------------------
/tag/templates/games_by_tag.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load bootstrap4 i18n staticfiles %}
3 | {% block content %}
4 |
5 |
6 |
7 | {% trans 'Games with tag: ' %}{{ selected_tag.name }} |
8 |
9 |
10 |
11 | {% for game in all_games_with_tag %}
12 |
13 |
14 |
15 | {{ game.title }} |
16 |
17 |
18 |
19 |
20 | {% trans 'Genre' %} |
21 |
22 | {% for genre in game.genre.all %}
23 |
24 | {% endfor %}
25 | |
26 |
27 |
28 | |
29 |
30 |
31 | {% trans 'Developer' %} |
32 | |
34 |
35 |
36 | {% trans 'Tags' %} |
37 |
38 | {% for tag in game.tags.all %}
39 | {{ tag.name }}
40 | {% endfor %}
41 | |
42 |
43 |
44 | {% trans 'Year' %} |
45 | {{ game.year }} |
46 |
47 |
48 |
49 | {% trans 'Delete' %} |
51 |
52 |
53 |
54 | {% endfor %}
55 | {% endblock %}
56 |
--------------------------------------------------------------------------------
/tag/templates/tag_create.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load bootstrap4 i18n staticfiles %}
3 | {% block content %}
4 |
5 |
6 |
7 | {% trans 'Create tag' %} |
8 |
9 |
10 |
11 |
12 | {% include 'message_block.html' %}
13 |
14 |
15 |
20 | |
21 |
22 |
23 | {% endblock %}
--------------------------------------------------------------------------------
/tag/templates/tag_list.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load bootstrap4 i18n staticfiles %}
3 | {% block content %}
4 |
5 |
6 |
7 | {% trans 'Tags' %} |
8 |
9 |
10 |
11 | {% if user.is_authenticatd %}
12 |
13 |
14 |
15 | {% trans 'Create' %}
17 | |
18 |
19 |
20 | {% endif %}
21 |
22 | {% include 'message_block.html' %}
23 | {% for tag in object_list %}
24 |
25 |
26 |
27 |
28 |
42 | |
43 |
44 |
45 |
46 | {% endfor %}
47 | {% endblock %}
48 |
--------------------------------------------------------------------------------
/tag/tests.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth.models import User
2 | from django.test import TestCase
3 | from django.urls import reverse
4 |
5 | from tag.models import Tag
6 | from .forms import AddTagForm
7 |
8 |
9 | class TagFormTests(TestCase):
10 |
11 | def test_genre_form(self):
12 | # redirects to login screen
13 | response = self.client.get(reverse('tag:tag-create'))
14 | self.assertEqual('/users/login?next=/pl/tag/tag_create/', response['location'])
15 | self.assertEquals(response.status_code, 302)
16 |
17 | self.client.force_login(User.objects.get_or_create(username='user_1')[0])
18 | response = self.client.get(reverse('tag:tag-create'))
19 | self.assertEquals(response.status_code, 200)
20 |
21 | form = AddTagForm()
22 | self.assertFalse(form.is_valid())
23 |
24 | data = {"name": ""}
25 | form = AddTagForm(data=data)
26 | self.assertFalse(form.is_valid())
27 | self.assertEqual(form.errors, {'name': ['To pole jest wymagane.']})
28 |
29 | data = {"name": "tag_1"}
30 | form = AddTagForm(data=data)
31 | self.assertTrue(form.is_valid())
32 |
33 |
34 | class TagListTests(TestCase):
35 |
36 | def setUp(self):
37 | Tag.objects.create(name='tag_1')
38 | Tag.objects.create(name='tag_2')
39 |
40 | def test_text_content(self):
41 | tag_object = Tag.objects.get(name='tag_1')
42 | expected_object_name = f'{tag_object.name}'
43 | self.assertEquals(expected_object_name, 'tag_1')
44 |
45 | def test_tag_list(self):
46 | response = self.client.get(reverse('tag:tag-list'))
47 | self.assertEqual(response.status_code, 200)
48 | self.assertContains(response, 'tag_1')
49 | self.assertEqual(len(response.context[0]['object_list']), 2)
50 | self.assertTemplateUsed(response, 'tag_list.html')
51 |
--------------------------------------------------------------------------------
/tag/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import path
2 |
3 | from . import views
4 |
5 | app_name = 'tag'
6 |
7 | urlpatterns = [
8 | path('tag_list/', views.TagListView.as_view(),
9 | name='tag-list'),
10 | path('tag_create/', views.TagCreateView.as_view(),
11 | name='tag-create'),
12 | path('tag_delete/', views.TagDeleteView.as_view(),
13 | name='tag-delete'),
14 | path('games_by_tag/', views.GamesByTagView.as_view(),
15 | name='games-by-tag')
16 | ]
17 |
--------------------------------------------------------------------------------
/tag/views.py:
--------------------------------------------------------------------------------
1 | from django.contrib import messages
2 | from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
3 | from django.http import HttpResponseRedirect
4 | from django.shortcuts import render
5 | from django.urls import reverse
6 | from django.utils.translation import ugettext as _
7 | from django.views import View
8 | from django.views.generic import ListView
9 |
10 | from game.models import Game
11 | from .forms import AddTagForm
12 | from .models import Tag
13 |
14 |
15 | class TagListView(ListView):
16 | model = Tag
17 | template_name = 'tag_list.html'
18 |
19 |
20 | class TagCreateView(LoginRequiredMixin, View):
21 | def get(self, request):
22 | return render(
23 | request,
24 | template_name='tag_create.html',
25 | context={'form': AddTagForm().as_p()}
26 | )
27 |
28 | def post(self, request):
29 | form = AddTagForm(request.POST)
30 | if form.is_valid():
31 | name = form.cleaned_data['name']
32 |
33 | if Tag.objects.filter(name=name).exists():
34 | messages.add_message(request, messages.WARNING, _('Tag with this name already exists'))
35 | return HttpResponseRedirect(reverse('tag:tag-create'))
36 |
37 | Tag.objects.create(name=name)
38 | messages.add_message(request, messages.INFO, _('Tag: {} created successfully').format(name))
39 | return HttpResponseRedirect(reverse('tag:tag-list'))
40 |
41 | messages.add_message(request, messages.ERROR, _('Form invalid'))
42 | return HttpResponseRedirect(reverse('tag:tag-create'))
43 |
44 |
45 | class TagDeleteView(PermissionRequiredMixin, View):
46 | permission_required = 'tag.delete_tag'
47 | raise_exception = True
48 |
49 | def get(self, request, tag_id):
50 | tag = Tag.objects.get(id=tag_id)
51 | tag.delete()
52 |
53 | return HttpResponseRedirect(reverse('tag:tag-list'))
54 |
55 |
56 | class GamesByTagView(View):
57 | def get(self, request, tag_id):
58 | """Display all games with selected tag"""
59 | selected_tag = Tag.objects.get(id=tag_id)
60 |
61 | ctx = {
62 | 'all_games_with_tag': Game.objects.filter(tags=selected_tag),
63 | 'selected_tag': selected_tag
64 | }
65 |
66 | return render(
67 | request,
68 | template_name='games_by_tag.html',
69 | context=ctx
70 | )
71 |
--------------------------------------------------------------------------------
/templates/base.html:
--------------------------------------------------------------------------------
1 |
2 | {% load bootstrap4 i18n staticfiles %}
3 |
4 |
5 |
6 | {% trans 'Gameweb' %}
7 |
8 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | {% if user.is_staff or user.is_superuser %}
20 | {% include 'navbar_staff.html' %}
21 | {% elif user.is_authenticated %}
22 | {% include 'navbar_user_authenticated.html' %}
23 | {% else %}
24 | {% include 'navbar_user_unauthenticated.html' %}
25 | {% endif %}
26 |
27 |
42 |
43 |

46 |
47 |
48 | {% block content %}
49 | {% endblock %}
50 |
51 |
52 |
53 |
56 |
59 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/templates/message_block.html:
--------------------------------------------------------------------------------
1 | {% if messages %}
2 | {% for message in messages %}
3 |
6 | {{ message }}
7 |
8 | {% endfor %}
9 | {% endif %}
--------------------------------------------------------------------------------
/templates/navbar_staff.html:
--------------------------------------------------------------------------------
1 | {% load bootstrap4 i18n staticfiles %}
2 |
--------------------------------------------------------------------------------
/templates/navbar_user_authenticated.html:
--------------------------------------------------------------------------------
1 | {% load bootstrap4 i18n staticfiles %}
2 |
--------------------------------------------------------------------------------
/templates/navbar_user_unauthenticated.html:
--------------------------------------------------------------------------------
1 | {% load bootstrap4 i18n staticfiles %}
2 |
--------------------------------------------------------------------------------
/users/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dawidbudzynski/gameweb_python_django/b95719f04b43d2cd0c804e5972a43648e05d9e39/users/__init__.py
--------------------------------------------------------------------------------
/users/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 |
3 | from .models import User
4 |
5 | admin.site.register(User)
6 |
--------------------------------------------------------------------------------
/users/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class UsersConfig(AppConfig):
5 | name = 'users'
6 |
--------------------------------------------------------------------------------
/users/forms.py:
--------------------------------------------------------------------------------
1 | from django.forms import (CharField, EmailField, Form, PasswordInput)
2 |
3 |
4 | class AddUserForm(Form):
5 | username = CharField(label='Username', strip=True)
6 | password = CharField(label='Password', widget=PasswordInput)
7 | first_name = CharField(label='First Name', strip=True, required=False)
8 | last_name = CharField(label='Surname', strip=True, required=False)
9 | email = EmailField(label='Email', required=False)
10 |
11 | def __init__(self, *args, **kwargs):
12 | super().__init__(*args, **kwargs)
13 | self.fields['username'].widget.attrs.update({'placeholder': 'Required'})
14 | self.fields['password'].widget.attrs.update({'placeholder': 'Required'})
15 |
16 |
17 | class LoginUserForm(Form):
18 | username = CharField(label='User', strip=True)
19 | password = CharField(label='Password', widget=PasswordInput)
20 |
--------------------------------------------------------------------------------
/users/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.1.2 on 2019-05-18 10:16
2 |
3 | from django.conf import settings
4 | from django.db import migrations, models
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | initial = True
10 |
11 | dependencies = [
12 | ('tag', '0001_initial'),
13 | ('genre', '0001_initial'),
14 | migrations.swappable_dependency(settings.AUTH_USER_MODEL),
15 | ('developer', '0001_initial'),
16 | ]
17 |
18 | operations = [
19 | migrations.CreateModel(
20 | name='User',
21 | fields=[
22 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
23 | ('favorite_developer', models.ForeignKey(blank=True, null=True, on_delete=None, to='developer.Developer')),
24 | ('favorite_genre', models.ForeignKey(blank=True, null=True, on_delete=None, to='genre.Genre')),
25 | ('favorite_tags', models.ManyToManyField(blank=True, to='tag.Tag')),
26 | ('user', models.ForeignKey(on_delete=None, to=settings.AUTH_USER_MODEL)),
27 | ],
28 | ),
29 | ]
30 |
--------------------------------------------------------------------------------
/users/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dawidbudzynski/gameweb_python_django/b95719f04b43d2cd0c804e5972a43648e05d9e39/users/migrations/__init__.py
--------------------------------------------------------------------------------
/users/models.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth.models import User as DjangoUser
2 | from django.db import models
3 |
4 |
5 | class User(models.Model):
6 | user = models.ForeignKey(DjangoUser, on_delete=None)
7 | favorite_tags = models.ManyToManyField('tag.Tag', blank=True)
8 | favorite_genre = models.ForeignKey('genre.Genre', blank=True, null=True, on_delete=None)
9 | favorite_developer = models.ForeignKey('developer.Developer', blank=True, null=True, on_delete=None)
10 |
11 | def __str__(self):
12 | return self.user.username
13 |
--------------------------------------------------------------------------------
/users/templates/user_create.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load bootstrap4 i18n staticfiles %}
3 | {% block content %}
4 |
5 |
6 |
7 | {% trans 'Create user' %} |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
19 | |
20 |
21 |
22 | {% endblock %}
23 |
--------------------------------------------------------------------------------
/users/templates/user_list.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load bootstrap4 i18n staticfiles %}
3 | {% block content %}
4 |
5 |
6 |
7 | {% trans 'User list' %} |
8 |
9 |
10 |
11 |
12 |
13 |
14 | {% trans 'Create user' %}
15 | |
16 |
17 |
18 |
19 | {% include 'message_block.html' %}
20 | {% for user in all_users %}
21 |
22 |
23 |
24 |
25 |
26 | {{ user.user.username }} |
27 |
28 |
29 | {% trans 'Full name' %} |
30 | {{ user.user.first_name }} {{ user.user.last_name }} |
31 |
32 |
33 | {% trans 'Email' %} |
34 | {{ user.user.email }} |
35 |
36 |
37 | {% trans 'Favorite tags' %} |
38 |
39 | {% for tag in user.favorite_tags.all %}
40 | {{ tag.name }}
41 | {% endfor %}
42 | |
43 |
44 |
45 | {% trans 'Favorite genre' %}
46 | |
47 |
48 | {% for genre in user.favorite_genre.all %}
49 | {{ genre.name }}
50 | {% endfor %}
51 | |
52 |
53 |
54 | {% trans 'Favorite developer' %} |
55 | {{ user.favorite_developer }} |
56 |
57 |
58 |
59 | {% trans 'Delete' %}
61 | |
62 |
63 |
64 |
65 | {% endfor %}
66 | {% endblock %}
67 |
68 |
69 |
--------------------------------------------------------------------------------
/users/templates/user_login.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load bootstrap4 i18n staticfiles %}
3 | {% block content %}
4 |
5 |
6 |
7 | {% trans 'Login' %} |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
19 | |
20 |
21 |
22 | {% endblock %}
23 |
--------------------------------------------------------------------------------
/users/tests.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth.models import User as DjangoUser
2 | from django.test import TestCase
3 | from django.urls import reverse
4 |
5 | from users.models import User
6 | from .forms import AddUserForm
7 |
8 |
9 | class UserFormTests(TestCase):
10 |
11 | def test_user_form(self):
12 | response = self.client.get(reverse('users:user-create'))
13 | self.assertEquals(response.status_code, 200)
14 |
15 | self.client.force_login(DjangoUser.objects.get_or_create(username='user_1', is_superuser=True)[0])
16 | response = self.client.get(reverse('users:user-create'))
17 | self.assertEquals(response.status_code, 200)
18 |
19 | form = AddUserForm()
20 | self.assertFalse(form.is_valid())
21 |
22 | data = {"name": "", 'password': ''}
23 | form = AddUserForm(data=data)
24 | self.assertFalse(form.is_valid())
25 | self.assertEqual(form.errors, {
26 | 'username': ['To pole jest wymagane.'],
27 | 'password': ['To pole jest wymagane.']
28 | })
29 |
30 | data = {"username": "user_1", 'password': 'password_1'}
31 | form = AddUserForm(data=data)
32 | self.assertTrue(form.is_valid())
33 |
34 |
35 | class UserViewsTests(TestCase):
36 |
37 | def setUp(self):
38 | django_user = DjangoUser.objects.create(
39 | username='user_1',
40 | password='password_1',
41 | first_name='first_name_1',
42 | last_name='last_name_1',
43 | email='email_1'
44 | )
45 | User.objects.create(user=django_user)
46 |
--------------------------------------------------------------------------------
/users/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import path
2 |
3 | from . import views
4 |
5 | app_name = 'users'
6 |
7 | urlpatterns = [
8 | path('user_list/', views.UserListView.as_view(),
9 | name='user_list'),
10 | path('user_create/', views.UserCreateView.as_view(),
11 | name='user-create'),
12 | path('user_delete/', views.UserDeleteView.as_view(),
13 | name='user-delete'),
14 | path('login', views.LoginView.as_view(),
15 | name='login'),
16 | path('logout', views.LogoutView.as_view(),
17 | name='logout'),
18 | ]
19 |
--------------------------------------------------------------------------------
/users/views.py:
--------------------------------------------------------------------------------
1 | from django.contrib import messages
2 | from django.contrib.auth import authenticate, login, logout
3 | from django.contrib.auth.models import User as DjangoUser
4 | from django.http import HttpResponseRedirect
5 | from django.shortcuts import render
6 | from django.urls import reverse
7 | from django.utils.translation import ugettext as _
8 | from django.views import View
9 |
10 | from .forms import AddUserForm, LoginUserForm
11 | from .models import User
12 |
13 |
14 | class UserListView(View):
15 | def dispatch(self, request, *args, **kwargs):
16 | if not (request.user.is_staff or request.user.is_superuser):
17 | messages.add_message(request, messages.WARNING, _('Sorry, you can\'t do that'))
18 | return HttpResponseRedirect(reverse('news_api:mainpage'))
19 | return super().dispatch(request, *args, **kwargs)
20 |
21 | def get(self, request):
22 | return render(
23 | request,
24 | template_name='user_list.html',
25 | context={'all_users': User.objects.all().order_by('user__username')}
26 | )
27 |
28 |
29 | class UserCreateView(View):
30 |
31 | def get(self, request):
32 | return render(
33 | request,
34 | template_name='user_create.html',
35 | context={'form': AddUserForm().as_p()}
36 | )
37 |
38 | def post(self, request):
39 | form = AddUserForm(request.POST)
40 | if form.is_valid():
41 |
42 | username = form.cleaned_data['username']
43 | password = form.cleaned_data['password']
44 | first_name = form.cleaned_data['first_name']
45 | last_name = form.cleaned_data['last_name']
46 | email = form.cleaned_data['email']
47 |
48 | if DjangoUser.objects.filter(username=username).exists():
49 | messages.add_message(request, messages.WARNING, _('User with this name already exists'))
50 | return HttpResponseRedirect(reverse('users:user-create'))
51 |
52 | django_user = DjangoUser.objects.create_user(
53 | username=username,
54 | password=password,
55 | first_name=first_name,
56 | last_name=last_name,
57 | email=email
58 | )
59 |
60 | User.objects.create(user=django_user)
61 | messages.add_message(request, messages.INFO, _('User: {} created successfully').format(username))
62 | return HttpResponseRedirect(reverse('users:user_list'))
63 |
64 | messages.add_message(request, messages.ERROR, _('Form invalid'))
65 | return HttpResponseRedirect(reverse('users:user-create'))
66 |
67 |
68 | class UserDeleteView(View):
69 |
70 | def dispatch(self, request, *args, **kwargs):
71 | if not (request.user.is_staff or request.user.is_superuser):
72 | messages.add_message(request, messages.WARNING, _('Sorry, you can\'t do that'))
73 | return HttpResponseRedirect(reverse('news_api:mainpage'))
74 | return super().dispatch(request, *args, **kwargs)
75 |
76 | def get(self, request, user_id):
77 | user = User.objects.get(id=user_id)
78 | user.delete()
79 | return HttpResponseRedirect(reverse('users:user-list'))
80 |
81 |
82 | class LoginView(View):
83 | def get(self, request):
84 | if request.session.get('loggedUser') is None:
85 | return render(
86 | request,
87 | template_name='user_login.html',
88 | context={'form': LoginUserForm().as_p()}
89 | )
90 | del request.session['loggedUser']
91 | return HttpResponseRedirect('/')
92 |
93 | def post(self, request):
94 | form = LoginUserForm(request.POST)
95 | if form.is_valid():
96 | username = form.cleaned_data['username']
97 | password = form.cleaned_data['password']
98 | request.session['loggedUser'] = username
99 | user = authenticate(username=username, password=password)
100 |
101 | if user is not None:
102 | login(request, user)
103 | return HttpResponseRedirect('/')
104 | else:
105 | messages.add_message(request, messages.ERROR, _('Wrong password'))
106 | return HttpResponseRedirect(reverse('users:user-create'))
107 |
108 | messages.add_message(request, messages.ERROR, _('Form invalid'))
109 | return HttpResponseRedirect(reverse('users:user-create'))
110 |
111 |
112 | class LogoutView(View):
113 | def get(self, request):
114 | logout(request)
115 | return HttpResponseRedirect('/')
116 |
--------------------------------------------------------------------------------
/utils/tests.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth.models import User as DjangoUser
2 |
3 | import random
4 |
5 | from developer.models import Developer
6 | from game.models import Game
7 | from genre.models import Genre
8 | from tag.models import Tag
9 | from users.models import User
10 |
11 |
12 | def populate_database():
13 | django_user = DjangoUser.objects.create(
14 | username='user_1',
15 | password='password_1',
16 | first_name='first_name_1',
17 | last_name='last_name_1',
18 | email='email_1'
19 | )
20 | User.objects.create(user=django_user)
21 | for i in range(1, 21):
22 | Tag.objects.create(name=f'tag_{i}')
23 | Genre.objects.create(name=f'genre_{i}')
24 | Developer.objects.create(name=f'developer_{i}')
25 | for i in range(1, 21):
26 | Game.objects.create(
27 | developer=Developer.objects.get(name='developer_{}'.format(random.randint(1, 20))),
28 | genre=Genre.objects.get(name='genre_{}'.format(random.randint(1, 20))),
29 | title=f'game_{i}',
30 | year=random.randint(2000, 2019),
31 | to_be_rated=random.choice([True, False])
32 | )
33 | for game in Game.objects.all():
34 | for tag in random.choices(list(Tag.objects.all()), k=5):
35 | game.tags.add(tag)
36 |
--------------------------------------------------------------------------------