├── .all-contributorsrc ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── ----bug-report.md │ ├── ---feature-request.md │ └── ---say-thank-you.md └── workflows │ └── label.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── Procfile ├── README.md ├── api ├── __init__.py ├── apps.py ├── migrations │ └── __init__.py ├── serializers.py ├── templates │ └── api.html ├── tests │ ├── __init__.py │ └── test_views.py ├── urls.py └── views.py ├── app ├── __init__.py ├── admin.py ├── apps.py ├── cache_constants.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20190829_1937.py │ └── __init__.py ├── models.py ├── resources.py ├── static │ └── app │ │ ├── TDB.png │ │ ├── computer.svg │ │ ├── computer2.svg │ │ ├── css │ │ └── customCSS.css │ │ ├── emoji1.png │ │ ├── fb.svg │ │ ├── github.svg │ │ ├── guy.svg │ │ ├── help.svg │ │ ├── icon-192.png │ │ ├── icon-512.png │ │ ├── js │ │ ├── burger.js │ │ ├── colorpad.js │ │ └── custom.js │ │ ├── linkedin.svg │ │ ├── manifest.json │ │ ├── patreon.png │ │ ├── search.png │ │ ├── search404.svg │ │ ├── tutorialdb.png │ │ └── twitter.svg ├── templates │ ├── about.html │ ├── base.html │ ├── contribute.html │ ├── home.html │ ├── latest.html │ ├── search_results.html │ ├── service-worker.js │ ├── taglinks.html │ ├── tags.html │ └── thankyou.html ├── tests │ ├── __init__.py │ └── test_views.py ├── urls.py └── views.py ├── manage.py ├── requirements.txt ├── runtime.txt ├── taggie ├── __init__.py ├── migrations │ └── __init__.py ├── parser.py └── tags.json └── tutorialdb ├── __init__.py ├── settings.py ├── urls.py └── wsgi.py /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "README.md" 4 | ], 5 | "imageSize": 100, 6 | "commit": false, 7 | "contributors": [ 8 | { 9 | "login": "Animesh-Ghosh", 10 | "name": "MaDDogx", 11 | "avatar_url": "https://avatars3.githubusercontent.com/u/34956994?v=4", 12 | "profile": "https://github.com/Animesh-Ghosh", 13 | "contributions": [ 14 | "code", 15 | "bug", 16 | "ideas", 17 | "review", 18 | "userTesting" 19 | ] 20 | }, 21 | { 22 | "login": "liorbentov", 23 | "name": "Lior Shub", 24 | "avatar_url": "https://avatars3.githubusercontent.com/u/8587019?v=4", 25 | "profile": "https://github.com/liorbentov", 26 | "contributions": [ 27 | "code", 28 | "bug", 29 | "design" 30 | ] 31 | }, 32 | { 33 | "login": "JGabrielGruber", 34 | "name": "José Gabriel Gruber", 35 | "avatar_url": "https://avatars0.githubusercontent.com/u/22822110?v=4", 36 | "profile": "https://www.crowbar.com.br", 37 | "contributions": [ 38 | "code" 39 | ] 40 | }, 41 | { 42 | "login": "Yegorov", 43 | "name": "Artem", 44 | "avatar_url": "https://avatars1.githubusercontent.com/u/2566462?v=4", 45 | "profile": "https://github.com/Yegorov", 46 | "contributions": [ 47 | "code" 48 | ] 49 | }, 50 | { 51 | "login": "viktorstrate", 52 | "name": "Viktor Strate Kløvedal", 53 | "avatar_url": "https://avatars3.githubusercontent.com/u/4233458?v=4", 54 | "profile": "https://svendborg-webdesign.dk/en", 55 | "contributions": [ 56 | "code" 57 | ] 58 | }, 59 | { 60 | "login": "chrisshyi", 61 | "name": "Chris Shyi", 62 | "avatar_url": "https://avatars1.githubusercontent.com/u/24416618?v=4", 63 | "profile": "https://github.com/chrisshyi", 64 | "contributions": [ 65 | "code" 66 | ] 67 | }, 68 | { 69 | "login": "vikneswaran20", 70 | "name": "vikneswaran", 71 | "avatar_url": "https://avatars1.githubusercontent.com/u/8945535?v=4", 72 | "profile": "https://github.com/vikneswaran20", 73 | "contributions": [ 74 | "code" 75 | ] 76 | }, 77 | { 78 | "login": "ObliviousParadigm", 79 | "name": "Adarsh Shetty", 80 | "avatar_url": "https://avatars0.githubusercontent.com/u/47667852?v=4", 81 | "profile": "https://github.com/ObliviousParadigm", 82 | "contributions": [ 83 | "doc" 84 | ] 85 | } 86 | ], 87 | "contributorsPerLine": 7, 88 | "projectName": "tutorialdb", 89 | "projectOwner": "Bhupesh-V", 90 | "repoType": "github", 91 | "repoHost": "https://github.com", 92 | "skipCi": true 93 | } 94 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: bhupesh 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/----bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41B Bug report" 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/---feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F680 Feature request" 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/---say-thank-you.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F49F Say thank you" 3 | about: Just say thanks if you liked tutorialdb 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | if you liked **tutorialdb** - please let us know. We'd love to hear from you! 11 | 12 | You can help me in any way possible 13 | 14 | - [ ] Give the repository a star ⭐️ 15 | - [ ] Help out with issues 16 | - [ ] Share tutorialdb with others 17 | - [ ] Make tutorials, This Powers tutorialdb 💪🏽 18 | - [ ] Support me on [Patreon](https://www.patreon.com/bePatron?u=18082750) 19 | 20 | Thank you! 💐 21 | -------------------------------------------------------------------------------- /.github/workflows/label.yml: -------------------------------------------------------------------------------- 1 | name: Labeler 2 | on: [pull_request] 3 | 4 | jobs: 5 | label: 6 | 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/labeler@v2 11 | with: 12 | repo-token: "${{ secrets.GITHUB_TOKEN }}" 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/python 3 | # Edit at https://www.gitignore.io/?templates=python 4 | 5 | ### Python ### 6 | # Byte-compiled / optimized / DLL files 7 | __pycache__/ 8 | *.py[cod] 9 | *$py.class 10 | 11 | # C extensions 12 | *.so 13 | 14 | # Distribution / packaging 15 | .Python 16 | build/ 17 | develop-eggs/ 18 | dist/ 19 | downloads/ 20 | eggs/ 21 | .eggs/ 22 | lib/ 23 | lib64/ 24 | parts/ 25 | sdist/ 26 | var/ 27 | wheels/ 28 | pip-wheel-metadata/ 29 | share/python-wheels/ 30 | *.egg-info/ 31 | .installed.cfg 32 | *.egg 33 | MANIFEST 34 | 35 | # PyInstaller 36 | # Usually these files are written by a python script from a template 37 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 38 | *.manifest 39 | *.spec 40 | 41 | # Installer logs 42 | pip-log.txt 43 | pip-delete-this-directory.txt 44 | 45 | # Unit test / coverage reports 46 | htmlcov/ 47 | .tox/ 48 | .nox/ 49 | .coverage 50 | .coverage.* 51 | .cache 52 | nosetests.xml 53 | coverage.xml 54 | *.cover 55 | .hypothesis/ 56 | .pytest_cache/ 57 | 58 | # Translations 59 | *.mo 60 | *.pot 61 | 62 | # Django stuff: 63 | *.log 64 | local_settings.py 65 | db.sqlite3 66 | 67 | # Flask stuff: 68 | instance/ 69 | .webassets-cache 70 | 71 | # Scrapy stuff: 72 | .scrapy 73 | 74 | # Sphinx documentation 75 | docs/_build/ 76 | 77 | # PyBuilder 78 | target/ 79 | 80 | # Jupyter Notebook 81 | .ipynb_checkpoints 82 | 83 | # IPython 84 | profile_default/ 85 | ipython_config.py 86 | 87 | # pyenv 88 | .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don’t work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # celery beat schedule file 98 | celerybeat-schedule 99 | 100 | # SageMath parsed files 101 | *.sage.py 102 | 103 | # Environments 104 | .env 105 | .venv 106 | env/ 107 | venv/ 108 | ENV/ 109 | env.bak/ 110 | venv.bak/ 111 | 112 | # Spyder project settings 113 | .spyderproject 114 | .spyproject 115 | 116 | # Rope project settings 117 | .ropeproject 118 | 119 | # mkdocs documentation 120 | /site 121 | 122 | # mypy 123 | .mypy_cache/ 124 | .dmypy.json 125 | dmypy.json 126 | 127 | # Pyre type checker 128 | .pyre/ 129 | 130 | # End of https://www.gitignore.io/api/python 131 | 132 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to tutorialdb 2 | 3 | 👍 🎉 First off, thanks for taking the time to contribute! 👍 🎉 4 | 5 | The following are a set of guidelines for contributing to tutorialdb. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a Pull Request. 6 | 7 | ## Table of Contents 8 | 9 | 1. [Getting Started](#getting-started) 10 | 2. [Submitting Pull Requests](#submitting-pull-requests) 11 | 12 | ### Getting Started 13 | 14 | - Make sure you communicate using [GitHub issues](https://github.com/Bhupesh-V/tutorialdb/issues) while submitting a PR or reporting bugs. 15 | - You can use an appropriate label to label the issue. 16 | - Feel free to ask doubts by opening an [issue](https://github.com/Bhupesh-V/tutorialdb/issues/new). 17 | 18 | ### Submitting Pull Requests 19 | 20 | **Before submitting a Pull Request, please make sure that there is a corresponding issue. If not, please provide a clear description, and title for your PR** 21 | 22 | Follow the steps given below while submitting a PR: 23 | 24 | 1. Create your own branch (never commit to master). 25 | 26 | ```bash 27 | git checkout -b 28 | ``` 29 | 30 | 2. Setup the development environment by following the [Installation](README.md#installation-) instructions. 31 | 3. Run the development server. 32 | 33 | ```bash 34 | python manage.py runserver 35 | ``` 36 | 37 | 4. Make your changes. 38 | 5. Run the tests. 39 | 40 | ```bash 41 | python manage.py test 42 | ``` 43 | 44 | 6. Push your changes to your branch. Make sure to comment the `SECRET_KEY` and `LOCAL_HOST` variables. 45 | 7. Create a Pull Request against the `master` branch. 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Bhupesh Varshney 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: gunicorn tutorialdb.wsgi --log-file - -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tutorialdb 2 | 3 | > A search engine for programming/dev tutorials. 4 | 5 |

6 | 7 |

8 | 9 | 10 | ![GitHub release](https://img.shields.io/github/release/Bhupesh-V/tutorialdb) 11 | [![GitHub license](https://img.shields.io/github/license/Bhupesh-V/tutorialdb)](https://github.com/Bhupesh-V/tutorialdb/blob/master/LICENSE) 12 | [![GitHub issues](https://img.shields.io/github/issues/Bhupesh-V/tutorialdb)](https://github.com/Bhupesh-V/tutorialdb/issues) 13 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/af7df141776744a49435a876c7b87834)](https://app.codacy.com/app/Bhupesh-V/tutorialdb?utm_source=github.com&utm_medium=referral&utm_content=Bhupesh-V/tutorialdb&utm_campaign=Badge_Grade_Dashboard) 14 | [![All Contributors](https://img.shields.io/badge/all_contributors-4-orange.svg?style=flat-square)](#contributors) 15 | [![CodeFactor](https://www.codefactor.io/repository/github/bhupesh-v/tutorialdb/badge)](https://www.codefactor.io/repository/github/bhupesh-v/tutorialdb) 16 | [![Maintainability](https://api.codeclimate.com/v1/badges/a4b8a52583b6706c0b3f/maintainability)](https://codeclimate.com/github/Bhupesh-V/tutorialdb/maintainability) 17 | 18 | ### About the Project 🔘 19 | 20 | - **tutorialdb** is a small scale search engine for programming/dev tutorials, it is meant to help anyone who is getting started to learn a new technology. 21 | - The sole purpose of tutorialdb is to help people get to resources which might help them learn new things for e.g sometimes there are tutorials on personal blogs which do not get indexed by Google easily. 22 | - All the content (tutorials) is owned by the respective authors/sites. 23 | - tutorialdb maintains its own database saving the links to tutorials and some meta info. 24 | 25 | ### Installation 🔮 26 | 27 | 1. Create virtual environment. 28 | 29 | **Linux/MacOS** 30 | ```bash 31 | virtualenv -p python3 venv && cd venv && source bin/activate 32 | ``` 33 | **Windows** 34 | (*PowerShell*) 35 | ```cmd 36 | py -m venv venv; .\venv\Scripts\activate; 37 | ``` 38 | 39 | 2. Clone the repository. 40 | 41 | ```bash 42 | git clone https://github.com/Bhupesh-V/tutorialdb.git 43 | ``` 44 | 45 | 3. Install dependencies. 46 | 47 | ```bash 48 | pip install -r requirements.txt 49 | ``` 50 | 51 | 4. Set-up virtual environment variables. 52 | 1. Create a file named `.env` in the root directory & add the following contents. 53 | 54 | ```text 55 | SECRET_KEY = 'my-secret-key' 56 | LOCAL_HOST = 'my-local-ip' 57 | ``` 58 | 2. For `SECRET_KEY` use [Django Secret Key Generator](https://www.miniwebtool.com/django-secret-key-generator/) or [Djecrety](https://djecrety.ir/). 59 | 3. Adding `LOCAL_HOST` is optional. 60 | 61 | 5. Migrate tables. 62 | 63 | ```bash 64 | python manage.py migrate 65 | ``` 66 | 67 | 6. Run Tests. 68 | 69 | ```bash 70 | python manage.py test 71 | ``` 72 | 73 | 7. Run the development server. 74 | 75 | ```bash 76 | python manage.py runserver 77 | ``` 78 | 79 | ## 📝 License 80 | 81 | This project is licensed under the MIT License. See the [LICENSE.md](LICENSE) file for details. 82 | 83 | ## 👋 Contributing 84 | 85 | Please read the [CONTRIBUTING](CONTRIBUTING.md) file for the process of submitting pull requests to us. 86 | 87 | ## ✨ Contributors 88 | 89 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 |

MaDDogx

💻 🐛 🤔 👀 📓

Lior Shub

💻 🐛 🎨

José Gabriel Gruber

💻

Artem

💻

Viktor Strate Kløvedal

💻

Chris Shyi

💻

vikneswaran

💻

Adarsh Shetty

📖
108 | 109 | 110 | 111 | 112 | 113 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! 114 | -------------------------------------------------------------------------------- /api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bhupesh-V/tutorialdb/b22c948aa573d06551544d9d64053b04bbbfd8d1/api/__init__.py -------------------------------------------------------------------------------- /api/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ApiConfig(AppConfig): 5 | name = 'api' 6 | -------------------------------------------------------------------------------- /api/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bhupesh-V/tutorialdb/b22c948aa573d06551544d9d64053b04bbbfd8d1/api/migrations/__init__.py -------------------------------------------------------------------------------- /api/serializers.py: -------------------------------------------------------------------------------- 1 | from app.models import Tag, Tutorial 2 | from rest_framework import serializers 3 | 4 | 5 | class TagSerializer(serializers.ModelSerializer): 6 | 7 | class Meta: 8 | model = Tag 9 | fields = ('__all__') 10 | 11 | 12 | class TutorialSerializer(serializers.ModelSerializer): 13 | tags = serializers.SlugRelatedField( 14 | many=True, 15 | read_only=True, 16 | slug_field='name' 17 | ) 18 | 19 | class Meta: 20 | model = Tutorial 21 | fields = ('__all__') 22 | 23 | 24 | class TutorialPOST(serializers.Serializer): 25 | """post a tutorial through the API""" 26 | 27 | class Meta: 28 | model = Tutorial 29 | fields = ('link', 'category') 30 | -------------------------------------------------------------------------------- /api/templates/api.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html'%} 2 | {% block content %} 3 |
4 |
5 |
6 |
7 |

8 | Are you a creator ?
9 | Try tutorialdb REST API 👇 10 |

11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | See Docs 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 44 | 45 | 46 | 47 | 48 | 54 | 55 | 56 | 57 | 58 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 |
RequestEndpointDescription
GET/api/tutorials

Returns all the tutorials.

POST/api/tutorials 36 |

Submit a tutorial.

37 | 38 | { 39 | "link":"https://abc.xyz", 40 | "category":"video" 41 | } 42 | 43 |
GET/api/tutorials/{tags}/ 49 |

Returns tutorials filtered by tags.
50 | Multiple Tags should be separated with commas,
51 | for e.g /api/tutorials/python,api 52 |

53 |
GET/api/tutorials/{tags}/{category}/ 59 |

Returns tutorials filtered by tags and category.
60 | Only one category can be specified at a time for e.g
61 | /api/tutorials/python,api/video or
62 | /api/tutorials/react/article 63 |

64 | Available Categories : 65 |
    66 |
  • article
  • 67 |
  • video
  • 68 |
  • docs
  • 69 |
  • course
  • 70 |
  • book
  • 71 |
  • cheatsheet
  • 72 |
73 |
GET/api/tags/

Returns all the tags.

GET/api/latest/

Return latest 10 tutorials from tutorialdb.

86 |
87 |
88 |
89 |
90 |
91 | {% endblock %} -------------------------------------------------------------------------------- /api/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bhupesh-V/tutorialdb/b22c948aa573d06551544d9d64053b04bbbfd8d1/api/tests/__init__.py -------------------------------------------------------------------------------- /api/tests/test_views.py: -------------------------------------------------------------------------------- 1 | from django.test import TransactionTestCase 2 | 3 | 4 | class APITests(TransactionTestCase): 5 | 6 | def test_tutorials_page_status_code(self): 7 | response = self.client.get('/tutorials/') 8 | self.assertEquals(response.status_code, 200) 9 | 10 | def test_tags_page_status_code(self): 11 | response = self.client.get('/tags/') 12 | self.assertEquals(response.status_code, 200) 13 | 14 | def test_latest_page_status_code(self): 15 | response = self.client.get('/latest/') 16 | self.assertEquals(response.status_code, 200) 17 | -------------------------------------------------------------------------------- /api/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from . import views 3 | 4 | urlpatterns = [ 5 | path('', views.index, name='api-home'), 6 | path('tutorials/', views.tutorials), 7 | path('tutorials//', views.tutorial_tag), 8 | path('tutorials///', views.tutorial_tag_category), 9 | path('tags/', views.tags), 10 | path('latest/', views.latest), 11 | ] 12 | -------------------------------------------------------------------------------- /api/views.py: -------------------------------------------------------------------------------- 1 | from app.models import Tag, Tutorial 2 | from django.http import HttpResponse 3 | from django.shortcuts import render 4 | from rest_framework import status 5 | from rest_framework.decorators import api_view 6 | from rest_framework.pagination import PageNumberPagination 7 | from rest_framework.renderers import JSONRenderer 8 | from taggie.parser import generate_tags 9 | from .serializers import TagSerializer, TutorialPOST, TutorialSerializer 10 | 11 | 12 | @api_view(['GET']) 13 | def index(request): 14 | """index view for api""" 15 | return render(request, 'api.html', {'title': 'API'}) 16 | 17 | 18 | class JSONResponse(HttpResponse): 19 | """wraps a simple HTTP Response to a JSON Response""" 20 | 21 | def __init__(self, data, **kwargs): 22 | content = JSONRenderer().render(data) 23 | kwargs['content_type'] = 'application/json' 24 | super(JSONResponse, self).__init__(content, **kwargs) 25 | 26 | 27 | @api_view(['GET', 'POST']) 28 | def tutorials(request): 29 | """ 30 | get: Return all tutorials 31 | post: submit a tutorial 32 | """ 33 | if request.method == 'GET': 34 | paginator = PageNumberPagination() 35 | tutorials = Tutorial.objects.all().order_by('id').filter(publish=True) 36 | context = paginator.paginate_queryset(tutorials, request) 37 | serializer = TutorialSerializer(context, many=True) 38 | return paginator.get_paginated_response(serializer.data) 39 | elif request.method == 'POST': 40 | postserializer = TutorialPOST(data=request.data) 41 | if postserializer.is_valid(): 42 | link_count = Tutorial.objects.filter( 43 | link=request.data['link']).count() 44 | if link_count == 0: 45 | tags, title = generate_tags(request.data['link']) 46 | if 'other' in tags: 47 | return JSONResponse( 48 | {"message ": "Not a Tutorial link"}, 49 | status=status.HTTP_406_NOT_ACCEPTABLE 50 | ) 51 | else: 52 | tutorial_object = Tutorial.objects.create( 53 | title=title, 54 | link=request.data['link'], 55 | category=request.data['category'] 56 | ) 57 | for tag in tags: 58 | obj, created = Tag.objects.get_or_create(name=tag) 59 | 60 | tag_obj_list = Tag.objects.filter(name__in=tags) 61 | tutorial_object.tags.set(tag_obj_list) 62 | return JSONResponse( 63 | {"message ": "Created, Thanks"}, status=status.HTTP_201_CREATED 64 | ) 65 | return JSONResponse( 66 | {"message ": "Created, Thanks"}, status=status.HTTP_201_CREATED 67 | ) 68 | return JSONResponse( 69 | {"message": "Not Valid, Try Again"}, status=status.HTTP_406_NOT_ACCEPTABLE 70 | ) 71 | 72 | 73 | @api_view(['GET']) 74 | def tutorial_tag(request, tags): 75 | """returns tutorials with {tags}""" 76 | paginator = PageNumberPagination() 77 | tags = tags.split(',') 78 | custom_tutorial = Tutorial.objects.filter( 79 | tags__name__in=tags).order_by('id').distinct().filter(publish=True) 80 | context = paginator.paginate_queryset(custom_tutorial, request) 81 | serializer = TutorialSerializer(context, many=True) 82 | return paginator.get_paginated_response(serializer.data) 83 | 84 | 85 | @api_view(['GET']) 86 | def tutorial_tag_category(request, tags, category): 87 | """return tutorials with {tags} and a {category}""" 88 | paginator = PageNumberPagination() 89 | tags = tags.split(',') 90 | category = category.split(',') 91 | custom_tutorial = Tutorial.objects.filter( 92 | tags__name__in=tags, category__in=category 93 | ).order_by('id').filter(publish=True).distinct() 94 | context = paginator.paginate_queryset(custom_tutorial, request) 95 | serializer = TutorialSerializer(context, many=True) 96 | return paginator.get_paginated_response(serializer.data) 97 | 98 | 99 | @api_view(['GET']) 100 | def tags(request): 101 | """returns all tags""" 102 | paginator = PageNumberPagination() 103 | tags = Tag.objects.all().order_by('id') 104 | context = paginator.paginate_queryset(tags, request) 105 | serializer = TagSerializer(context, many=True) 106 | return paginator.get_paginated_response(serializer.data) 107 | 108 | 109 | @api_view(['GET']) 110 | def latest(request): 111 | """returns latest 10 tutorials from tutorialdb""" 112 | paginator = PageNumberPagination() 113 | results = Tutorial.objects.all().filter(publish=True).order_by('-created_date')[:10] 114 | context = paginator.paginate_queryset(results, request) 115 | serializer = TutorialSerializer(context, many=True) 116 | return paginator.get_paginated_response(serializer.data) 117 | -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bhupesh-V/tutorialdb/b22c948aa573d06551544d9d64053b04bbbfd8d1/app/__init__.py -------------------------------------------------------------------------------- /app/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from import_export.admin import ImportExportModelAdmin 3 | from .models import Tag, Tutorial 4 | from .resources import TagResource, TutorialResource 5 | 6 | @admin.register(Tag) 7 | class TagAdmin(ImportExportModelAdmin): 8 | resource_class = TagResource 9 | 10 | 11 | @admin.register(Tutorial) 12 | class TutorialAdmin(ImportExportModelAdmin, admin.ModelAdmin): 13 | list_display = ('id', 'title', 'publish') 14 | resource_class = TutorialResource 15 | -------------------------------------------------------------------------------- /app/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class AppConfig(AppConfig): 5 | name = 'app' 6 | -------------------------------------------------------------------------------- /app/cache_constants.py: -------------------------------------------------------------------------------- 1 | """ 2 | List of all Cache key constants. 3 | """ 4 | 5 | ALL_TAGS = "ALL_TAGS" 6 | -------------------------------------------------------------------------------- /app/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.3 on 2019-07-16 14:46 2 | 3 | from django.db import migrations, models 4 | import django.utils.timezone 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | initial = True 10 | 11 | dependencies = [ 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='tag', 17 | fields=[ 18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('name', models.CharField(max_length=100)), 20 | ('created_date', models.DateTimeField(default=django.utils.timezone.now)), 21 | ('description', models.TextField(blank=True)), 22 | ], 23 | ), 24 | migrations.CreateModel( 25 | name='tutorial', 26 | fields=[ 27 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 28 | ('title', models.CharField(max_length=200)), 29 | ('link', models.URLField()), 30 | ('category', models.CharField(choices=[('article', 'article'), ('video', 'video'), ('course', 'course'), ('docs', 'docs')], max_length=200)), 31 | ('created_date', models.DateTimeField(default=django.utils.timezone.now)), 32 | ('tags', models.ManyToManyField(to='app.tag')), 33 | ], 34 | ), 35 | ] 36 | -------------------------------------------------------------------------------- /app/migrations/0002_auto_20190829_1937.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.3 on 2019-08-29 14:07 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('app', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='tutorial', 15 | name='publish', 16 | field=models.BooleanField(default=False), 17 | ), 18 | migrations.AlterField( 19 | model_name='tutorial', 20 | name='category', 21 | field=models.CharField(choices=[('article', 'Article'), ('book', 'Book'), ('cheatsheet', 'Cheatsheet'), ('course', 'Course'), ('docs', 'Documentation'), ('video', 'Video')], max_length=20), 22 | ), 23 | migrations.AlterField( 24 | model_name='tutorial', 25 | name='tags', 26 | field=models.ManyToManyField(to='app.Tag'), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /app/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bhupesh-V/tutorialdb/b22c948aa573d06551544d9d64053b04bbbfd8d1/app/migrations/__init__.py -------------------------------------------------------------------------------- /app/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.utils import timezone 3 | 4 | 5 | class Tag(models.Model): 6 | """tags have a name, creation date and maybe description""" 7 | name = models.CharField(max_length=100) 8 | created_date = models.DateTimeField(default=timezone.now) 9 | description = models.TextField(blank=True) 10 | 11 | def __str__(self): 12 | return self.name 13 | 14 | 15 | class Tutorial(models.Model): 16 | """tutorials have a title, a URL, a set of tags, a category and creation date""" 17 | ARTICLE = 'article' 18 | BOOK = 'book' 19 | CHEATSHEET = 'cheatsheet' 20 | COURSE = 'course' 21 | DOCS = 'docs' 22 | VIDEO = 'video' 23 | 24 | CATEGORIES = ( 25 | (ARTICLE, 'Article'), 26 | (BOOK, 'Book'), 27 | (CHEATSHEET, 'Cheatsheet'), 28 | (COURSE, 'Course'), 29 | (DOCS, 'Documentation'), 30 | (VIDEO, 'Video'), 31 | ) 32 | 33 | title = models.CharField(max_length=200) 34 | link = models.URLField() 35 | tags = models.ManyToManyField(Tag) 36 | category = models.CharField(max_length=20, choices=CATEGORIES) 37 | created_date = models.DateTimeField(default=timezone.now) 38 | publish = models.BooleanField(default=False) 39 | 40 | def __str__(self): 41 | return self.title 42 | -------------------------------------------------------------------------------- /app/resources.py: -------------------------------------------------------------------------------- 1 | from import_export import fields, resources 2 | from import_export.widgets import ManyToManyWidget 3 | from . models import Tutorial, Tag 4 | 5 | 6 | class TutorialResource(resources.ModelResource): 7 | tags = fields.Field( 8 | column_name='tags', 9 | attribute='tags', 10 | widget=ManyToManyWidget(Tag, ',', 'name')) 11 | 12 | class Meta: 13 | model = Tutorial 14 | exclude = ('id',) 15 | export_order = ('title', 'link', 'tags', 'category', 'created_date', 'publish') 16 | import_id_fields = ('title', 'link') 17 | 18 | 19 | class TagResource(resources.ModelResource): 20 | 21 | class Meta: 22 | model = Tag 23 | exclude = ('id',) 24 | export_order = ('name', 'description', 'created_date') 25 | import_id_fields = ('name', 'description') 26 | -------------------------------------------------------------------------------- /app/static/app/TDB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bhupesh-V/tutorialdb/b22c948aa573d06551544d9d64053b04bbbfd8d1/app/static/app/TDB.png -------------------------------------------------------------------------------- /app/static/app/computer.svg: -------------------------------------------------------------------------------- 1 | Asset 560X -------------------------------------------------------------------------------- /app/static/app/computer2.svg: -------------------------------------------------------------------------------- 1 | Asset 410 -------------------------------------------------------------------------------- /app/static/app/css/customCSS.css: -------------------------------------------------------------------------------- 1 | a.navbar-item { 2 | cursor: pointer; 3 | color: white; 4 | } 5 | .navbar-menu { 6 | background-color: #2B2828; 7 | box-shadow: 0 8px 16px rgba(10,10,10,.1); 8 | padding: .5rem 0; 9 | } 10 | .customBurger{ 11 | background-color: white; 12 | } 13 | #shareIcon { 14 | display: none; 15 | } 16 | @media only screen and (min-device-width : 320px) and (max-device-width : 480px){ 17 | #shareIcon { 18 | display: inline; 19 | } 20 | } 21 | #tutorial-tag { 22 | color: blue; 23 | } 24 | 25 | summary.title { 26 | cursor: pointer; 27 | outline: none; 28 | float: left; 29 | } 30 | 31 | summary.title + .table-container { 32 | clear: both; 33 | } 34 | -------------------------------------------------------------------------------- /app/static/app/emoji1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bhupesh-V/tutorialdb/b22c948aa573d06551544d9d64053b04bbbfd8d1/app/static/app/emoji1.png -------------------------------------------------------------------------------- /app/static/app/fb.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | 13 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/static/app/github.svg: -------------------------------------------------------------------------------- 1 | GitHub icon -------------------------------------------------------------------------------- /app/static/app/guy.svg: -------------------------------------------------------------------------------- 1 | Asset 300 -------------------------------------------------------------------------------- /app/static/app/help.svg: -------------------------------------------------------------------------------- 1 | Asset 290 -------------------------------------------------------------------------------- /app/static/app/icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bhupesh-V/tutorialdb/b22c948aa573d06551544d9d64053b04bbbfd8d1/app/static/app/icon-192.png -------------------------------------------------------------------------------- /app/static/app/icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bhupesh-V/tutorialdb/b22c948aa573d06551544d9d64053b04bbbfd8d1/app/static/app/icon-512.png -------------------------------------------------------------------------------- /app/static/app/js/burger.js: -------------------------------------------------------------------------------- 1 | document.addEventListener('DOMContentLoaded', () => { 2 | 3 | // Get all "navbar-burger" elements 4 | const $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0); 5 | 6 | // Check if there are any navbar burgers 7 | if ($navbarBurgers.length > 0) { 8 | 9 | // Add a click event on each of them 10 | $navbarBurgers.forEach( el => { 11 | el.addEventListener('click', () => { 12 | 13 | // Get the target from the "data-target" attribute 14 | const target = el.dataset.target; 15 | const $target = document.getElementById(target); 16 | 17 | // Toggle the "is-active" class on both the "navbar-burger" and the "navbar-menu" 18 | el.classList.toggle('is-active'); 19 | $target.classList.toggle('is-active'); 20 | 21 | }); 22 | }); 23 | } 24 | 25 | }); -------------------------------------------------------------------------------- /app/static/app/js/colorpad.js: -------------------------------------------------------------------------------- 1 | var elem = document.getElementsByName("colorpad"); 2 | 3 | for (var j = 0; j < elem.length; j++){ 4 | elem[j].style.backgroundColor = randomColor({luminosity: 'light'}); 5 | } -------------------------------------------------------------------------------- /app/static/app/js/custom.js: -------------------------------------------------------------------------------- 1 | setTimeout(function focusMe() { 2 | const elem = document.getElementById("focus-here"); 3 | if (elem) { 4 | elem.scrollIntoView(); 5 | } 6 | }); 7 | 8 | function share(title, link) { 9 | if (navigator.share) { 10 | navigator.share({ 11 | title: title, 12 | url: link, 13 | }) 14 | .then(() => console.log('Successful Share')) 15 | .catch((error) => console.log('Error sharing', error)); 16 | } 17 | } 18 | 19 | function checkEmpty() { 20 | var input = document.getElementById("search-bar"); 21 | if (input.value === "" || input.value === null) { 22 | return false; 23 | } 24 | return true; 25 | } 26 | -------------------------------------------------------------------------------- /app/static/app/linkedin.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | 12 | 14 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/static/app/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "tutorialdb", 3 | "name": "tutorialdb", 4 | "icons": [ 5 | { 6 | "src": "/static/app/icon-192.png", 7 | "type": "image/png", 8 | "sizes": "192x192" 9 | }, 10 | { 11 | "src": "/static/app/icon-512.png", 12 | "type": "image/png", 13 | "sizes": "512x512" 14 | } 15 | ], 16 | "start_url": "/", 17 | "display": "standalone", 18 | "theme_color": "#2B2828", 19 | "background_color": "#ffffff" 20 | } 21 | -------------------------------------------------------------------------------- /app/static/app/patreon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bhupesh-V/tutorialdb/b22c948aa573d06551544d9d64053b04bbbfd8d1/app/static/app/patreon.png -------------------------------------------------------------------------------- /app/static/app/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bhupesh-V/tutorialdb/b22c948aa573d06551544d9d64053b04bbbfd8d1/app/static/app/search.png -------------------------------------------------------------------------------- /app/static/app/search404.svg: -------------------------------------------------------------------------------- 1 | Asset 180 -------------------------------------------------------------------------------- /app/static/app/tutorialdb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bhupesh-V/tutorialdb/b22c948aa573d06551544d9d64053b04bbbfd8d1/app/static/app/tutorialdb.png -------------------------------------------------------------------------------- /app/static/app/twitter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 10 | 11 | 12 | 13 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/templates/about.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html'%} 2 | {% load staticfiles %} 3 | {% block content %} 4 |
5 |
6 |

About

7 |
8 |
9 | computer

10 |
11 |
    12 |
  • 13 | tutorialdb is a small scale search engine for programming/dev tutorials, it is meant to help anyone who is getting started to learn a new technology. 14 |
  • 15 |
  • 16 | The sole purpose of tutorialdb is to help people get to resoures which might help them learn new things for e.g sometimes there are tutorials on personal blogs which do not get indexed in google easily. 17 |
  • 18 |
  • 19 | All the content (tutorials) is owned by the respective authors/sites.
    20 | tutorialdb maintains its own database saving the links to tutorials and some meta info. 21 |
  • 22 |
23 |
24 |
25 |
26 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/base.html: -------------------------------------------------------------------------------- 1 | {% load staticfiles %} 2 | 3 | 4 | 5 | {% if title %} 6 | tutorialdb - {{ title }} 7 | {% else %} 8 | tutorialdb 9 | {% endif %} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 78 | {% block content %}{% endblock content %} 79 |
80 |
81 |

82 | tutorialdb built with 💓 and 🎶 by Bhupesh Varshney. 83 | The source is licensed under MIT 84 |

85 |
86 | 87 | patreon 88 | 89 |
90 |
91 | twitter 92 | github 93 | facebook 94 | linkedin 95 |
96 |
97 | 98 | 99 | 100 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /app/templates/contribute.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load staticfiles %} 3 | {% block content %} 4 |
5 |
6 |
7 |

8 | Found a bug 🐛 ❓
9 | Have a feature ✨ request ❓
10 | create an 11 | issue 12 |

13 |
14 |
15 |
16 |
17 |
18 |
19 |

Or just,
20 | help 21 |
Help others by submitting tutorials you follow.
22 | 🤗 23 |

24 |
25 |
26 |
27 |
28 |
29 | {% if error %} 30 | {{ error }} 31 | {% endif %} 32 |
33 |

34 |
35 | {% csrf_token %} 36 |
37 | 38 |
39 | 40 |
41 |
42 |
43 | 44 |
45 |
46 | 52 |
53 |
54 |
55 |
56 |
57 | 58 |
59 |
60 |
61 |
62 |
63 |
64 | {% endblock content %} -------------------------------------------------------------------------------- /app/templates/home.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load staticfiles %} 3 | {% block content %} 4 |
5 |
6 |
7 |
8 | tutorialdb 9 |
10 |

A search engine for programming/dev tutorials.

11 | computer 12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |

🙅🏽‍♂️ No useless stuff or ads.

20 |

✅ Direct Links to tutorials.

21 |

🚀 Start learning asap.

22 |
23 |
24 |
25 |
26 |
27 |
28 |

Search Tutorials

29 |
30 | search 31 |
32 |
33 |
34 |
35 |
36 | 37 | 38 | 39 | 40 |
41 |
42 |
43 | 44 |
45 |
46 |
47 | 57 |
58 |
59 |
60 |
61 |
62 | 63 |
64 |
65 |
66 |
67 | {% block results %}{% endblock results %} 68 | {% endblock content %} 69 | -------------------------------------------------------------------------------- /app/templates/latest.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html'%} 2 | {% load staticfiles %} 3 | {% block content %} 4 | {% if tutorials %} 5 |
6 |

Latest in tutorialdb

7 |
8 | {% for tutorial in tutorials %} 9 |
10 | {{ tutorial.title }} 11 |
12 | {% for tag in tutorial.tags.all %} 13 | 14 | 📌 {{ tag }} 15 |   16 | {% endfor %} 17 | 19 |
20 |
21 | {{ tutorial.category }} 22 | 23 |
24 | {% endfor %} 25 |
26 |
27 | {% else %} 28 |

29 | search404 30 |
Ah! Sorry, No Results 🙀 31 |

32 | {% endif %} 33 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/search_results.html: -------------------------------------------------------------------------------- 1 | {% extends 'home.html' %} 2 | {% load staticfiles %} 3 | {% block results %} 4 |
5 | {% if tutorials %} 6 |
7 |

Search Results for "{{ query }}"

8 |

About {{ total }} results ({{ time }} seconds)


9 |
10 | {% for tutorial in tutorials %} 11 |
12 | {{ tutorial.title }} 13 |
14 | {% for tag in tutorial.tags.all %} 15 | 16 | 📌 {{ tag }} 17 |   18 | {% endfor %} 19 | 21 |
22 |
23 | {{ tutorial.category }} 24 | 25 |
26 | {% endfor %} 27 |
28 |
29 |
30 |
31 | {% if tutorials.has_other_pages %} 32 | 53 | {% endif %} 54 |
55 |
56 | {% else %} 57 |

58 | Ah! Sorry, No Results
59 | search404 60 |

61 | {% endif %} 62 |
63 | {% endblock results %} -------------------------------------------------------------------------------- /app/templates/service-worker.js: -------------------------------------------------------------------------------- 1 | var CACHE_NAME = 'tutorial-db-cache-v1'; 2 | var urlsToCache = [ 3 | '/', 4 | '/static/app/css/customCSS.css', 5 | '/static/app/js/burger.js', 6 | '/static/app/js/colorpad.js', 7 | '/static/app/js/custom.js', 8 | ]; 9 | 10 | self.addEventListener('install', function(event) { 11 | // Perform install steps 12 | event.waitUntil( 13 | caches.open(CACHE_NAME) 14 | .then(function(cache) { 15 | return cache.addAll(urlsToCache); 16 | }) 17 | ); 18 | }); 19 | 20 | self.addEventListener('fetch', function(event) { 21 | event.respondWith( 22 | caches.match(event.request) 23 | .then(function(response) { 24 | // Cache hit - return response 25 | if (response) { 26 | return response; 27 | } 28 | return fetch(event.request); 29 | } 30 | ) 31 | ); 32 | }); -------------------------------------------------------------------------------- /app/templates/taglinks.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block content %} 3 | {% load staticfiles %} 4 | {% if tutorials %} 5 |
6 |

Tutorials Tagged 7 | 8 | #{{ tag }} 9 | 10 |

11 |
12 | {% for tutorial in tutorials %} 13 |
14 | {{ tutorial.title }} 15 |
16 | {% for tag in tutorial.tags.all %} 17 | 18 | 📌 {{ tag }} 19 |   20 | {% endfor %} 21 | 23 |
24 |
25 | {{ tutorial.category }} 26 | 27 |
28 | {% endfor %} 29 |
30 | 31 |
32 |
33 | See All Tags 34 |
35 |
36 |
37 | {% else %} 38 |

39 | Ah! Sorry, No Results
40 | search404 41 |

42 | {% endif %} 43 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/tags.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load staticfiles %} 3 | {% block content %} 4 |
5 |
6 |

Tags

7 | {% if tags %} 8 | {% for tag in tags %} 9 | 10 | 📌 {{ tag.name }} 11 |   12 | {% endfor %} 13 | {% endif %} 14 |
15 |
16 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/thankyou.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html'%} 2 | {% load staticfiles %} 3 | {% block content %} 4 |
5 |
6 |
7 |
8 |

9 | Thanks
10 | hug 11 |

12 | guy 13 |
14 |
15 |
16 | Submit More 17 |
18 |
19 | Search Tutorials 20 |
21 |
22 |
23 |
24 |
25 | {% endblock %} -------------------------------------------------------------------------------- /app/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bhupesh-V/tutorialdb/b22c948aa573d06551544d9d64053b04bbbfd8d1/app/tests/__init__.py -------------------------------------------------------------------------------- /app/tests/test_views.py: -------------------------------------------------------------------------------- 1 | from django.test import SimpleTestCase, TransactionTestCase 2 | 3 | 4 | class StaticPageTests(SimpleTestCase): 5 | 6 | def test_home_page_status_code(self): 7 | response = self.client.get('/') 8 | self.assertEquals(response.status_code, 200) 9 | 10 | def test_api_page_status_code(self): 11 | response = self.client.get('/api/') 12 | self.assertEquals(response.status_code, 200) 13 | 14 | def test_about_page_status_code(self): 15 | response = self.client.get('/about/') 16 | self.assertEquals(response.status_code, 200) 17 | 18 | def test_contribute_page_status_code(self): 19 | response = self.client.get('/contribute/') 20 | self.assertEquals(response.status_code, 200) 21 | 22 | 23 | class DynamicPageTests(TransactionTestCase): 24 | 25 | def test_latest_page_status_code(self): 26 | response = self.client.get('/latest/') 27 | self.assertEquals(response.status_code, 200) 28 | 29 | def test_tags_page_status_code(self): 30 | response = self.client.get('/tags/') 31 | self.assertEquals(response.status_code, 200) 32 | 33 | 34 | class TestTemplateNames(TransactionTestCase): 35 | 36 | def test_home_page_name(self): 37 | response = self.client.get('/') 38 | self.assertTemplateUsed(template_name='home.html') 39 | 40 | def test_api_page_name(self): 41 | response = self.client.get('/api/') 42 | self.assertTemplateUsed(template_name='api.html') 43 | 44 | def test_about_page_name(self): 45 | response = self.client.get('/about/') 46 | self.assertTemplateUsed(template_name='about.html') 47 | 48 | def test_contribute_page_name(self): 49 | response = self.client.get('/contribute/') 50 | self.assertTemplateUsed(template_name='contribute.html') 51 | 52 | def test_latest_page_name(self): 53 | response = self.client.get('/latest/') 54 | self.assertTemplateUsed(template_name='latest.html') 55 | 56 | def test_tags_page_name(self): 57 | response = self.client.get('/tags/') 58 | self.assertTemplateUsed(template_name='tags.html') 59 | 60 | -------------------------------------------------------------------------------- /app/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import include 2 | from django.urls import path 3 | from django.views.generic import TemplateView 4 | from . import views 5 | from .views import ContributeView, HomePageView 6 | 7 | app_name = 'app' 8 | urlpatterns = [ 9 | path('', HomePageView.as_view(), name='home'), 10 | path('search/', views.search_query, name='search-results'), 11 | path('api/', include('api.urls'), name='api'), 12 | path('latest/', views.latest, name='latest'), 13 | path('tags/', views.tags, name='tags'), 14 | path('tags/tag=', views.taglinks, name='tag-links'), 15 | path('about/', views.about, name='about'), 16 | path('contribute/', ContributeView.as_view(), name='contribute'), 17 | path('service-worker.js', TemplateView.as_view( 18 | template_name="service-worker.js", 19 | content_type='application/javascript', 20 | ), name='service-worker.js'), 21 | ] -------------------------------------------------------------------------------- /app/views.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from django.core.cache import cache 4 | from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator 5 | from django.db.models import Q 6 | from django.shortcuts import render 7 | from django.views.generic import TemplateView 8 | from taggie.parser import generate_tags 9 | from .models import Tag, Tutorial 10 | from . import cache_constants 11 | 12 | 13 | class HomePageView(TemplateView): 14 | template_name = 'home.html' 15 | context = {} 16 | 17 | def get_context_data(self, **kwargs): 18 | """providing additional context""" 19 | self.context = super().get_context_data(**kwargs) 20 | self.context['categories'] = Tutorial.CATEGORIES 21 | return self.context 22 | 23 | 24 | def search_query(request): 25 | """view for the search results""" 26 | query = request.GET.get('q').lower() 27 | category = request.GET.get('category') 28 | list_query = query.split() 29 | 30 | start_time = time.time() 31 | 32 | if category is not None: 33 | tutorials = Tutorial.objects.filter( 34 | (Q(title__icontains=query) | Q(tags__name__in=list_query)) 35 | & Q(category__icontains=category) 36 | ).order_by('id').filter(publish=True).distinct() 37 | else: 38 | tutorials = Tutorial.objects.filter( 39 | (Q(title__icontains=query) | Q(tags__name__in=list_query)) 40 | ).order_by('id').filter(publish=True).distinct() 41 | end_time = time.time() 42 | total = len(tutorials) 43 | result_time = round(end_time - start_time, 3) 44 | 45 | paginator = Paginator(tutorials, 3) 46 | page = request.GET.get('page') 47 | try: 48 | tutorials = paginator.page(page) 49 | except PageNotAnInteger: 50 | tutorials = paginator.page(1) 51 | except EmptyPage: 52 | tutorials = paginator.page(paginator.num_pages) 53 | 54 | context = { 55 | 'query': query, 56 | 'category': category, 57 | 'tutorials': tutorials, 58 | 'total': total, 59 | 'time': result_time, 60 | 'title': query, 61 | 'categories': Tutorial.CATEGORIES 62 | } 63 | 64 | return render(request, 'search_results.html', context) 65 | 66 | 67 | def latest(request): 68 | """view for the latest tutorial entries""" 69 | tutorials = Tutorial.objects.all().filter(publish=True).order_by('-id')[:10] 70 | context = { 71 | 'tutorials': tutorials, 72 | 'title': 'Latest' 73 | } 74 | return render(request, 'latest.html', context) 75 | 76 | 77 | def tags(request): 78 | """view for the tags""" 79 | tags = cache.get_or_set(cache_constants.ALL_TAGS, Tag.objects.all(), None) 80 | context = { 81 | 'tags': tags, 82 | 'title': 'Tags' 83 | } 84 | return render(request, 'tags.html', context) 85 | 86 | 87 | def taglinks(request, tagname): 88 | """view for the tutorials with the {tagname}""" 89 | taglist = [] 90 | taglist.append(tagname) 91 | tutorials = Tutorial.objects.filter(tags__name__in=taglist, publish=True) 92 | context = { 93 | 'tag': tagname, 94 | 'tutorials': tutorials, 95 | 'title': tagname 96 | } 97 | return render(request, 'taglinks.html', context) 98 | 99 | 100 | def about(request): 101 | """about view""" 102 | return render(request, 'about.html', {'title': 'About'}) 103 | 104 | 105 | class ContributeView(TemplateView): 106 | """view for the tutorial contribution page""" 107 | context = { 108 | 'title': 'Contribute', 109 | 'categories': Tutorial.CATEGORIES 110 | } 111 | 112 | def get(self, request): 113 | """GET the contribution form""" 114 | try: 115 | if 'error' in self.context.keys(): 116 | self.context.popitem() 117 | except KeyError: 118 | pass 119 | 120 | return render(request, 'contribute.html', self.context) 121 | 122 | def post(self, request): 123 | """POST a tutorial""" 124 | link_count = Tutorial.objects.filter( 125 | link=request.POST['tlink']).count() 126 | if link_count == 0: 127 | tags, title = generate_tags(request.POST['tlink']) 128 | self.context['error'] = 'Not a Tutorial Link, Try Again' 129 | if 'other' in tags: 130 | return render( 131 | request, 132 | 'contribute.html', 133 | self.context 134 | ) 135 | else: 136 | tutorial_object = Tutorial.objects.create( 137 | title=title, 138 | link=request.POST['tlink'], 139 | category=request.POST['tcategory'] 140 | ) 141 | for tag in tags: 142 | obj, created = Tag.objects.get_or_create(name=tag) 143 | 144 | tag_obj_list = Tag.objects.filter(name__in=tags) 145 | tutorial_object.tags.set(tag_obj_list) 146 | 147 | # clearing the all tags cache 148 | cache.delete(cache_constants.ALL_TAGS) 149 | # thankyou.html shouldn't be accessible unless someone successfully posts 150 | # a tutorial 151 | return render(request, 'thankyou.html', {'title': 'Thanks!'}) 152 | return render(request, 'thankyou.html', {'title': 'Thanks!'}) 153 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tutorialdb.settings') 9 | try: 10 | from django.core.management import execute_from_command_line 11 | except ImportError as exc: 12 | raise ImportError( 13 | "Couldn't import Django. Are you sure it's installed and " 14 | "available on your PYTHONPATH environment variable? Did you " 15 | "forget to activate a virtual environment?" 16 | ) from exc 17 | execute_from_command_line(sys.argv) 18 | 19 | 20 | if __name__ == '__main__': 21 | main() 22 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django>=2.2.0 2 | requests>=2.20.0 3 | beautifulsoup4>=4.7.0 4 | lxml>=4.4.0 5 | djangorestframework>=3.10.0 6 | python-dotenv>=0.10.0 7 | django-import-export>=1.0.0 8 | whitenoise==3.3.1 9 | dj-database-url==0.4.2 10 | gunicorn==19.7.1 11 | psycopg2==2.7.3.2 -------------------------------------------------------------------------------- /runtime.txt: -------------------------------------------------------------------------------- 1 | python-3.6.8 -------------------------------------------------------------------------------- /taggie/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bhupesh-V/tutorialdb/b22c948aa573d06551544d9d64053b04bbbfd8d1/taggie/__init__.py -------------------------------------------------------------------------------- /taggie/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bhupesh-V/tutorialdb/b22c948aa573d06551544d9d64053b04bbbfd8d1/taggie/migrations/__init__.py -------------------------------------------------------------------------------- /taggie/parser.py: -------------------------------------------------------------------------------- 1 | import re 2 | import json 3 | import os 4 | import requests 5 | 6 | from bs4 import BeautifulSoup as bs 7 | from django.conf import settings 8 | 9 | TAG_LOCATION = os.path.join(settings.BASE_DIR, 'taggie/tags.json') 10 | 11 | 12 | def valid_tag(tag): 13 | """checks if a tag is valid with respect to tags.json""" 14 | with open(TAG_LOCATION) as json_file: 15 | data = json.load(json_file) 16 | 17 | return tag in data['tags'] 18 | 19 | # to be used for testing or from shell 20 | def total_tags(): 21 | """returns the total number of tags present in tags.json""" 22 | with open(TAG_LOCATION) as json_file: 23 | data = json.load(json_file) 24 | 25 | return f'{len(data["tags"])}' 26 | 27 | 28 | def tokenize_tutorial(title, description, generated_tags): 29 | """tokenizes the tutorial""" 30 | pattern = re.compile(r'\W+^[\+.]') 31 | 32 | title_list = list(re.sub(pattern, '', title).lower().split(" ")) 33 | meta_list = list(re.sub(pattern, '', description).lower().split(" ")) 34 | 35 | generated_tags += list(filter(valid_tag, list(title_list + meta_list))) 36 | 37 | if len(generated_tags) == 0: 38 | generated_tags.append('other') 39 | 40 | generated_tags = ' '.join(generated_tags).split() 41 | 42 | return list(set(generated_tags)) 43 | 44 | 45 | def parse_tutorial(res): 46 | """parses the tutorial page""" 47 | temporary_tags = [] 48 | 49 | html = bs(res.text, "lxml") 50 | 51 | og_description = html.find('meta', property="og:description") 52 | description = html.find('meta', property="description") 53 | keywords = html.find(attrs={'name': 'keywords'}) 54 | twitter_title = html.find('meta', property="twitter:title") 55 | 56 | tutorial_title = str(html.title.text).strip() 57 | 58 | for tag in html.find_all('meta', property="article:tag"): 59 | if tag is not None: 60 | temporary_tags.append(str(tag.get("content")).lower()) 61 | 62 | temporary_tags = list(filter(valid_tag, temporary_tags)) 63 | 64 | if og_description is None and description is not None: 65 | tutorial_description = str(description.get("content")) 66 | if description is None and og_description is not None: 67 | tutorial_description = str(og_description.get("content")) 68 | if og_description is None and description is None: 69 | tutorial_description = "null" 70 | if keywords is not None: 71 | keywords = keywords.get("content").replace(',', ' ') 72 | tutorial_description = f'{tutorial_description}{" "}{keywords}' 73 | if twitter_title is not None: 74 | twitter_title = twitter_title.get("content") 75 | tutorial_description = f'{tutorial_description}{" "}{twitter_title}' 76 | 77 | return tutorial_title, tutorial_description, temporary_tags 78 | 79 | 80 | def get_tutorial(link): 81 | """get request to the tutorial link""" 82 | res = None 83 | try: 84 | res = requests.get(link, headers={ 85 | 'User-Agent': 86 | 'Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36' 87 | + '(KHTML, like Gecko) Chrome/28.0.1500.52 Safari/537.36' 88 | }) 89 | if res.status_code == 200: 90 | return res 91 | elif res.status_code == 403: 92 | raise Exception('Unautorized Access') 93 | elif res.status_code == 404: 94 | raise Exception('{} Not Found'.format(link)) 95 | 96 | except requests.exceptions.InvalidURL as e: 97 | raise Exception(e) 98 | except requests.exceptions.MissingSchema as e: 99 | raise Exception(e) 100 | except requests.exceptions.ConnectionError as e: 101 | raise Exception(e) 102 | 103 | 104 | def generate_tags(link): 105 | """generates tutorial tags""" 106 | response = get_tutorial(link) 107 | tutorial_title, tutorial_description, temporary_tags = parse_tutorial( 108 | response) 109 | tutorial_tags = tokenize_tutorial( 110 | tutorial_title, tutorial_description, temporary_tags) 111 | 112 | if 'ci/cd' in tutorial_tags: 113 | tutorial_tags[tutorial_tags.index('ci/cd')] = 'ci-cd' 114 | 115 | return tutorial_tags, tutorial_title 116 | -------------------------------------------------------------------------------- /taggie/tags.json: -------------------------------------------------------------------------------- 1 | { 2 | "tags": [ 3 | "c++", "c++11", "c++14", "c++17", 4 | "python", "python2", "python3", "angular", 5 | "react", "javascript", "php", "html", 6 | "css", "vue", "git", "vscode", 7 | "linux", "opensource", "java", "ruby", 8 | "swift", "go", "rails", "oop", 9 | "node", "docker", "devops", "aws", 10 | "sql", "rust", "django", "android", 11 | "frontend", "backend", "fullstack", "design", 12 | "mvc", "github", "gitlab", "typescript", 13 | "bash", "ubuntu", "vim", "ios", 14 | "dotnet", "csharp", "laravel", "reactnative", 15 | "machinelearning", "serverless", "graphql", 16 | "kotlin", "elixir", "agile", "kubernetes", 17 | "mongodb", "ux", "flutter", "haskell", 18 | "cloud", "database", "wordpress", "blockchain", 19 | "c", "webdev", "arduino", "web", 20 | "framework", "library", "app", "crud", 21 | "drupal", "security", "js", "nuxt.js", 22 | "next.js", "vue.js", "react.js", "api", 23 | "rest", "portfolio", "prototyping", "monorepo", 24 | "chrome", "firefox", "unreal", "http", 25 | "https", "cors", "statistics", "gatsby", 26 | "rss", "responsive", "jira", "c#", 27 | "style", "clojure", "pytorch", "tensorflow", 28 | "numpy", "pandas", "opencv", "keras", 29 | "mlpack", "r", "ai", "ml.net", 30 | ".net", "objective-c", "testing", "asp", 31 | "asp.net", "vb", "vb.net", "shell", 32 | "ecmascript", "erlang", "gnu", "octave", 33 | "terminal", "cmd", "scala", "probability", 34 | "heroku", "netlify", "bitbucket", "career", 35 | "computerscience", "ci/cd", "programming", "azure", 36 | "babel", "dart", "cheatsheet", "elm", 37 | "microservices", "oss", "perl", "pwa", 38 | "redux", "ajax", "webassembly", "regex", 39 | "books", "roadmap", "algorithms", "data structures", 40 | "stl", "windows", "unix", "json", 41 | "algorithm", "web", "travis", "bot", 42 | "chatbot", "sass", "scss", "100daysofcode", 43 | "300daysofcode", "todayilearned", "til", "es6", 44 | "apache", "ember.js", "container", "gcc", 45 | "safari", "css3", "html5", "xml", 46 | "jekyll", "disrtibuted", "os", "ionic", 47 | "spring", "matlab", "swing", "firebase", 48 | "qt", "maven", "selenium", "csv", 49 | "unity", "xamarin", "jenkins", "caching", 50 | "boost", "webview", "oauth", "pyspark", 51 | "sqlalchemy", "multiprocessing", "multithreading", "opengl", 52 | "resume", "software", "iot", "nlp", 53 | "d3.js", "redis", "debugging", "networking", 54 | "tcp/ip", "interview", "productivity", "alexa", 55 | "visualbasic", "solidity", "rubyonrails", "node.js", 56 | "jquery", "electron", "postgressql", "bootstrap", 57 | "flask", "mysql", "seo", "react-native", 58 | "elasticsearch", "cryptocurrency", "ui" 59 | ] 60 | } -------------------------------------------------------------------------------- /tutorialdb/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bhupesh-V/tutorialdb/b22c948aa573d06551544d9d64053b04bbbfd8d1/tutorialdb/__init__.py -------------------------------------------------------------------------------- /tutorialdb/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | import dj_database_url 3 | 4 | from dotenv import load_dotenv 5 | load_dotenv() 6 | 7 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 8 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 9 | 10 | SECRET_KEY = os.environ['SECRET_KEY'] 11 | 12 | try: 13 | LOCAL_HOST = os.environ['LOCAL_HOST'] # your local IP to test the site on your network 14 | except: 15 | LOCAL_HOST = None 16 | 17 | DEBUG = True 18 | 19 | ALLOWED_HOSTS = [ 20 | '127.0.0.1', 21 | 'tutorialdb.pythonanywhere.com', 22 | 'tutorialdb-app.herokuapp.com', 23 | ] 24 | 25 | if LOCAL_HOST: 26 | ALLOWED_HOSTS.append(LOCAL_HOST) 27 | 28 | # Application definition 29 | 30 | INSTALLED_APPS = [ 31 | 'django.contrib.admin', 32 | 'django.contrib.auth', 33 | 'django.contrib.contenttypes', 34 | 'django.contrib.sessions', 35 | 'django.contrib.messages', 36 | 'django.contrib.staticfiles', 37 | 'rest_framework', 38 | 'app', 39 | 'api', 40 | 'taggie', 41 | 'import_export', 42 | ] 43 | 44 | REST_FRAMEWORK = { 45 | 'DEFAULT_THROTTLE_CLASSES': [ 46 | 'rest_framework.throttling.AnonRateThrottle', 47 | 'rest_framework.throttling.UserRateThrottle' 48 | ], 49 | 'DEFAULT_THROTTLE_RATES': { 50 | 'anon': '100/day', 51 | 'user': '100/day' 52 | }, 53 | 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination', 54 | 'PAGE_SIZE': 10 55 | } 56 | 57 | MIDDLEWARE = [ 58 | 'whitenoise.middleware.WhiteNoiseMiddleware', 59 | 'django.middleware.security.SecurityMiddleware', 60 | 'django.contrib.sessions.middleware.SessionMiddleware', 61 | 'django.middleware.common.CommonMiddleware', 62 | 'django.middleware.csrf.CsrfViewMiddleware', 63 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 64 | 'django.contrib.messages.middleware.MessageMiddleware', 65 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 66 | ] 67 | 68 | ROOT_URLCONF = 'tutorialdb.urls' 69 | 70 | TEMPLATES = [ 71 | { 72 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 73 | 'DIRS': [os.path.join(BASE_DIR, 'templates')], 74 | 'APP_DIRS': True, 75 | 'OPTIONS': { 76 | 'context_processors': [ 77 | 'django.template.context_processors.debug', 78 | 'django.template.context_processors.request', 79 | 'django.contrib.auth.context_processors.auth', 80 | 'django.contrib.messages.context_processors.messages', 81 | ], 82 | }, 83 | }, 84 | ] 85 | 86 | WSGI_APPLICATION = 'tutorialdb.wsgi.application' 87 | 88 | # Database 89 | # https://docs.djangoproject.com/en/2.2/ref/settings/#databases 90 | 91 | DATABASES = { 92 | 'default': { 93 | 'ENGINE': 'django.db.backends.sqlite3', 94 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 95 | } 96 | } 97 | 98 | # Password validation 99 | # https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators 100 | 101 | AUTH_PASSWORD_VALIDATORS = [ 102 | { 103 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 104 | }, 105 | { 106 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 107 | }, 108 | { 109 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 110 | }, 111 | { 112 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 113 | }, 114 | ] 115 | 116 | # Internationalization 117 | # https://docs.djangoproject.com/en/2.2/topics/i18n/ 118 | 119 | LANGUAGE_CODE = 'en-IN' 120 | 121 | TIME_ZONE = 'Asia/Kolkata' 122 | 123 | USE_I18N = True 124 | 125 | USE_L10N = True 126 | 127 | USE_TZ = True 128 | 129 | PROJECT_ROOT = os.path.join(os.path.abspath(__file__)) 130 | 131 | # Location of all static files 132 | STATIC_URL = '/static/' 133 | STATIC_ROOT = os.path.join(PROJECT_ROOT, 'staticfiles') 134 | 135 | # Extra lookup directories for collectstatic to find static files 136 | STATICFILES_DIRS = ( 137 | os.path.join(PROJECT_ROOT, 'static'), 138 | ) 139 | 140 | STATICFILES_STORAGE = 'whitenoise.django.GzipManifestStaticFilesStorage' 141 | 142 | prod_db = dj_database_url.config(conn_max_age=500) 143 | DATABASES['default'].update(prod_db) -------------------------------------------------------------------------------- /tutorialdb/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import include 2 | from django.contrib import admin 3 | from django.urls import path 4 | 5 | urlpatterns = [ 6 | path('', include('app.urls')), 7 | path('api/', include('api.urls')), 8 | path('admin/', admin.site.urls), 9 | ] 10 | -------------------------------------------------------------------------------- /tutorialdb/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for tutorialdb 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.2/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', 'tutorialdb.settings') 15 | 16 | application = get_wsgi_application() 17 | --------------------------------------------------------------------------------