├── .github └── workflows │ ├── dev-tests.yml │ ├── python-package.yml │ └── version-bump.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── MANIFEST.in ├── README.rst ├── docs ├── css │ └── extra.css └── index.md ├── mkdocs.yml ├── pyproject.toml ├── requirements.txt ├── rest_friendship ├── __init__.py ├── apps.py ├── serializers.py ├── urls.py └── views.py ├── setup.cfg ├── setup.py ├── tests ├── __init__.py ├── conftest.py ├── factories.py ├── models.py ├── serializers.py ├── test_views.py └── urls.py └── tox.ini /.github/workflows/dev-tests.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | # Does not change version or update codecov as opposed to main test suite 4 | 5 | name: Dev Tests 6 | 7 | on: 8 | push: 9 | tags: 10 | - '*' 11 | branches: [ testing ] 12 | pull_request: 13 | branches: [ dev, testing ] 14 | 15 | jobs: 16 | build: 17 | runs-on: ubuntu-latest 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | python-version: ["3.8", "3.9", "3.10"] 22 | 23 | steps: 24 | - uses: actions/checkout@v2 25 | - name: Set up Python ${{ matrix.python-version }} 26 | uses: actions/setup-python@v2 27 | with: 28 | python-version: ${{ matrix.python-version }} 29 | - name: Install dependencies 30 | run: | 31 | python -m pip install --upgrade pip 32 | python -m pip install tox tox-gh-actions 33 | - name: Lint with flake8 34 | run: | 35 | tox -e flake8 36 | - name: Test repo with tox 37 | run: tox -v 38 | - name: Coverage Erase 39 | run: tox -e clean 40 | versionbump: 41 | runs-on: ubuntu-latest 42 | steps: 43 | - uses: actions/checkout@v2 44 | with: 45 | fetch-depth: 0 46 | - name: 'Get latest tag' 47 | id: previoustag 48 | uses: "WyriHaximus/github-action-get-previous-tag@v1" 49 | - name: Update rest_friendship/__init__.py file with version tag. 50 | run: | 51 | echo "__version__ = '"${{ steps.previoustag.outputs.tag }}"'" > ./rest_friendship/__init__.py 52 | - name: Commit files 53 | continue-on-error: true 54 | run: | 55 | git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" 56 | git config --local user.name "github-actions[bot]" 57 | git add ./rest_friendship/__init__.py 58 | git commit -m "Auto-bump version to "${{ steps.previoustag.outputs.tag }}"" -a 59 | - name: Push changes 60 | continue-on-error: true 61 | uses: ad-m/github-push-action@master 62 | with: 63 | github_token: ${{ secrets.GITHUB_TOKEN }} 64 | branch: testing 65 | force: true 66 | 67 | -------------------------------------------------------------------------------- /.github/workflows/python-package.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: Rest Friendship Tests 5 | on: 6 | push: 7 | tags: 8 | - '*' 9 | branches: [ master ] 10 | pull_request: 11 | branches: [ master ] 12 | release: 13 | branches: [ master ] 14 | 15 | jobs: 16 | build: 17 | runs-on: ubuntu-latest 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | python-version: ["3.8", "3.9", "3.10"] 22 | 23 | steps: 24 | - uses: actions/checkout@v2 25 | - name: Set up Python ${{ matrix.python-version }} 26 | uses: actions/setup-python@v2 27 | with: 28 | python-version: ${{ matrix.python-version }} 29 | - name: Install dependencies 30 | run: | 31 | python -m pip install --upgrade pip 32 | python -m pip install tox tox-gh-actions 33 | - name: Lint with flake8 34 | run: tox -e flake8 35 | - name: Test with tox 36 | run: tox -v 37 | - name: Upload coverage to Codecov 38 | uses: codecov/codecov-action@v2 39 | with: 40 | python-version: "3.10" 41 | 42 | -------------------------------------------------------------------------------- /.github/workflows/version-bump.yml: -------------------------------------------------------------------------------- 1 | # This workflow will check the latest tag on the branch and apply it to 2 | # the __init__.py file for rest_friendship 3 | 4 | name: New Tag Version Bump 5 | 6 | on: 7 | push: 8 | tags: 9 | - '*' 10 | branches: [ master ] 11 | 12 | jobs: 13 | versionbump: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v2 17 | with: 18 | fetch-depth: 0 19 | - name: 'Get latest tag' 20 | id: previoustag 21 | uses: "WyriHaximus/github-action-get-previous-tag@v1" 22 | - name: Update rest_friendship/__init__.py file with version tag. 23 | run: | 24 | echo "__version__ = '"${{ steps.previoustag.outputs.tag }}"'" > ./rest_friendship/__init__.py 25 | - name: Commit files 26 | continue-on-error: true 27 | run: | 28 | git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" 29 | git config --local user.name "github-actions[bot]" 30 | git add ./rest_friendship/__init__.py 31 | git commit -m "Auto-bump version to "${{ steps.previoustag.outputs.tag }}"" -a 32 | - name: Push changes 33 | continue-on-error: true 34 | uses: ad-m/github-push-action@master 35 | with: 36 | github_token: ${{ secrets.GITHUB_TOKEN }} 37 | branch: master 38 | force: true 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-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 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | .vscode 113 | 114 | # Spyder project settings 115 | .spyderproject 116 | .spyproject 117 | 118 | # Rope project settings 119 | .ropeproject 120 | 121 | # mkdocs documentation 122 | /site 123 | /html 124 | 125 | # mypy 126 | .mypy_cache/ 127 | .dmypy.json 128 | dmypy.json 129 | 130 | # Pyre type checker 131 | .pyre/ 132 | 133 | # Ignore Django Migrations in Development if you are working on team 134 | # Only for Development only 135 | **/migrations/** 136 | !**/migrations 137 | !**/migrations/__init__.py 138 | *.pyc 139 | *.code-workspace 140 | 141 | !.gitignore 142 | !.travis.yml 143 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | sudo: false 4 | 5 | env: 6 | - TOX_ENV=py310-docs 7 | - TOX_ENV=py310-django4.0.1-drf3.13.1 8 | 9 | matrix: 10 | fast_finish: true 11 | 12 | install: 13 | - pip install tox 14 | 15 | script: 16 | - tox -e $TOX_ENV 17 | 18 | python: 19 | - 3.8 20 | - 3.9 21 | - 3.10 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017, Diego Navarro Mellén 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-exclude * __pycache__ 2 | recursive-exclude * *.py[co] 3 | recursive-include tests *.py 4 | recursive-include rest_friendship *.py _version.txt 5 | recursive-include docs *.rst *.css *.md 6 | include *.py 7 | include *.rst 8 | include *.txt 9 | include mkdocs.yml 10 | include MANIFEST.in 11 | include LICENSE 12 | include pytest.ini 13 | include tox.ini 14 | prune .github -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | django-rest-friendship 2 | ====================== 3 | 4 | |PyPI| |Build| |coverage| 5 | 6 | .. |PyPI| image:: https://img.shields.io/pypi/v/django-rest-friendship 7 | 8 | .. |Build| image:: https://img.shields.io/github/workflow/status/dnmellen/django-rest-friendship/Python%20package 9 | 10 | .. |coverage| image:: https://img.shields.io/codecov/c/gh/dnmellen/django-rest-friendship 11 | 12 | Overview 13 | -------- 14 | 15 | DRF endpoints for django-friendship 16 | 17 | Requirements 18 | ------------ 19 | 20 | - Python (3.8, 3.9, 3.10) 21 | - Django (3.2, 4.0) 22 | - Django REST Framework (3.13.1) 23 | 24 | Installation 25 | ------------ 26 | 27 | Install using ``pip``\ … 28 | 29 | .. code:: bash 30 | 31 | pip install django-rest-friendship 32 | 33 | Add rest_friendship to your ``INSTALLED_APPS`` 34 | 35 | .. code:: python 36 | 37 | INSTALLED_APPS = ( 38 | ... 39 | 'friendship', # Django friendship 40 | 'rest_framework', # Django REST Framework 41 | 'rest_friendship', # Django REST Friendship 42 | 'rest_framework.authtoken', 43 | ... 44 | ) 45 | 46 | Also add settings for ``REST_FRIENDSHIP`` to your project ``settings.py`` 47 | 48 | .. code:: python 49 | 50 | REST_FRIENDSHIP = { 51 | 'PERMISSION_CLASSES': [ 52 | 'rest_framework.permissions.IsAuthenticated', 53 | ], 54 | 'USER_SERIALIZER': 'rest_friendship.serializers.FriendSerializer', 55 | }, 56 | 57 | And don’t forget to add the following to your project ``urls.py`` 58 | 59 | .. code:: python 60 | 61 | urlpatterns = [ 62 | ... 63 | path('', include('rest_friendship.urls')), 64 | ... 65 | ] 66 | 67 | Examples 68 | -------- 69 | 70 | Get Friends List 71 | ^^^^^^^^^^^^^^^^ 72 | 73 | .. code:: bash 74 | 75 | curl -LX GET http://127.0.0.1:8000/friends/ -H 'Authorization: Token 16bd63ca6655a5fe8d25d7c8bb1b42605c77088b' 76 | 77 | [{"id":1,"username":"testuser","email":"testuser@piboy.ca"}] 78 | 79 | Add/Remove Friends 80 | ^^^^^^^^^^^^^^^^^^ 81 | 82 | .. code:: bash 83 | 84 | curl -X POST http://127.0.0.1:8000/friends/add_friend/ -H 'Authorization: Token 16bd63ca6655a5fe8d25d7c8bb1b42605c77088b' --data 'to_user=testuser&message=Hello+friend' 85 | 86 | {"id":4,"from_user":"scott@gmail.com","to_user":"testuser@piboy.ca","message":"Hello friend","created":"2022-01-22T04:21:43.593950Z","rejected":null,"viewed":null} 87 | 88 | .. code:: bash 89 | 90 | curl -X POST http://127.0.0.1:8000/friends/remove_friend/ -H 'Authorization: Token 16bd63ca6655a5fe8d25d7c8bb1b42605c77088b' --data 'to_user=testuser' 91 | 92 | [{"message": "Friend deleted"}] 93 | 94 | Accept a Request with request ID 95 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 96 | 97 | .. code:: bash 98 | 99 | curl -X POST http://127.0.0.1:8000/friends/accept_request/ -H 'Authorization: Token 16bd63ca6655a5fe8d25d7c8bb1b42605c77088b' --data 'id=1' 100 | 101 | {"message": "Request accepted, user added to friends."} 102 | 103 | Testing 104 | ------- 105 | 106 | Install testing requirements and run with ``pytest``: 107 | 108 | .. code:: bash 109 | 110 | pip install django-rest-friendship[test] 111 | 112 | pytest 113 | 114 | You can also use the excellent `tox `__ testing tool to run the 115 | tests against all supported versions of Python and Django. Install tox 116 | globally, and then simply run: 117 | 118 | .. code:: bash 119 | 120 | tox -s 121 | 122 | Documentation 123 | ------------- 124 | 125 | To build the documentation, you’ll need to install ``mkdocs``. 126 | 127 | .. code:: bash 128 | 129 | pip install django-rest-friendship[docs] 130 | 131 | To preview the documentation: 132 | 133 | .. code:: bash 134 | 135 | $ mkdocs serve 136 | Running at: http://127.0.0.1:8000 137 | 138 | To build the documentation: 139 | 140 | .. code:: bash 141 | 142 | mkdocs build 143 | -------------------------------------------------------------------------------- /docs/css/extra.css: -------------------------------------------------------------------------------- 1 | body.homepage div.col-md-9 h1:first-of-type { 2 | text-align: center; 3 | font-size: 60px; 4 | font-weight: 300; 5 | margin-top: 0; 6 | } 7 | 8 | body.homepage div.col-md-9 p:first-of-type { 9 | text-align: center; 10 | } 11 | 12 | body.homepage .badges { 13 | text-align: right; 14 | } 15 | 16 | body.homepage .badges a { 17 | display: inline-block; 18 | } 19 | 20 | body.homepage .badges a img { 21 | padding: 0; 22 | margin: 0; 23 | } 24 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # django-rest-friendship 2 | 3 | ![PyPI](https://img.shields.io/pypi/v/django-rest-friendship) 4 | ![Build](https://img.shields.io/github/workflow/status/dnmellen/django-rest-friendship/Python%20package) 5 | ![coverage](https://img.shields.io/codecov/c/gh/dnmellen/django-rest-friendship) 6 | 7 | ## Overview 8 | 9 | DRF endpoints for django-friendship 10 | 11 | ## Requirements 12 | 13 | - Python (3.8, 3.9, 3.10) 14 | - Django (3.2, 4.0) 15 | - Django REST Framework (3.13.1) 16 | 17 | ## Installation 18 | 19 | Install using `pip`... 20 | 21 | ```bash 22 | pip install django-rest-friendship 23 | ``` 24 | 25 | Add rest_friendship to your `INSTALLED_APPS` 26 | 27 | ```python 28 | INSTALLED_APPS = ( 29 | ... 30 | 'friendship', # Django friendship 31 | 'rest_framework', # Django REST Framework 32 | 'rest_friendship', # Django REST Friendship 33 | 'rest_framework.authtoken', 34 | ... 35 | ) 36 | ``` 37 | 38 | Also add settings for `REST_FRIENDSHIP` to your `settings.py` 39 | 40 | ```python 41 | REST_FRIENDSHIP = { 42 | 'PERMISSION_CLASSES': [ 43 | 'rest_framework.permissions.IsAuthenticated', 44 | ], 45 | 'USER_SERIALIZER': 'rest_friendship.serializers.FriendSerializer', 46 | }, 47 | ``` 48 | 49 | And don't forget to add the following to your project `urls.py` 50 | 51 | ```python 52 | urlpatterns = [ 53 | ... 54 | path('', include('rest_friendship.urls')), 55 | ... 56 | ] 57 | ``` 58 | 59 | ## Examples 60 | 61 | ##### Get Friends List 62 | 63 | ```bash 64 | curl -LX GET http://127.0.0.1:8000/friends/ -H 'Authorization: Token 16bd63ca6655a5fe8d25d7c8bb1b42605c77088b' 65 | 66 | [{"id":1,"username":"testuser","email":"testuser@piboy.ca"}] 67 | ``` 68 | 69 | ##### Add/Remove Friends 70 | 71 | ```bash 72 | curl -X POST http://127.0.0.1:8000/friends/add_friend/ -H 'Authorization: Token 16bd63ca6655a5fe8d25d7c8bb1b42605c77088b' --data 'to_user=testuser&message=Hello+friend' 73 | 74 | {"id":4,"from_user":"scott@gmail.com","to_user":"testuser@piboy.ca","message":"Hello friend","created":"2022-01-22T04:21:43.593950Z","rejected":null,"viewed":null} 75 | ``` 76 | 77 | ```bash 78 | curl -X POST http://127.0.0.1:8000/friends/remove_friend/ -H 'Authorization: Token 16bd63ca6655a5fe8d25d7c8bb1b42605c77088b' --data 'to_user=testuser' 79 | 80 | [{"message": "Friend deleted"}] 81 | ``` 82 | 83 | ##### Accept a Request with request ID 84 | 85 | ```bash 86 | curl -X POST http://127.0.0.1:8000/friends/accept_request/ -H 'Authorization: Token 16bd63ca6655a5fe8d25d7c8bb1b42605c77088b' --data 'id=1' 87 | 88 | {"message": "Request accepted, user added to friends."} 89 | ``` 90 | 91 | ## Testing 92 | 93 | Install testing requirements and run with `pytest`: 94 | 95 | ```bash 96 | pip install django-rest-friendship[test] 97 | 98 | pytest 99 | ``` 100 | 101 | You can also use the excellent 102 | [tox](http://tox.readthedocs.org/en/latest/) testing tool to run the 103 | tests against all supported versions of Python and Django. Install tox 104 | globally, and then simply run: 105 | 106 | ```bash 107 | tox -s 108 | ``` 109 | 110 | ## Documentation 111 | 112 | To build the documentation, you'll need to install `mkdocs`. 113 | 114 | ```bash 115 | pip install django-rest-friendship[docs] 116 | ``` 117 | 118 | To build the documentation: 119 | 120 | ```bash 121 | mkdocs build 122 | ``` 123 | 124 | To preview the documentation: 125 | 126 | ```bash 127 | $ mkdocs serve 128 | Running at: http://127.0.0.1:8000 129 | ``` 130 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: django-rest-friendship 2 | site_description: DRF endpoints for django-friendship 3 | repo_url: https://github.com/dnmellen/django-rest-friendship 4 | site_dir: html 5 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools>=60.5.0", 4 | "wheel" 5 | ] 6 | build-backend = "setuptools.build_meta" -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # Minimum Django and REST framework version 2 | Django>=3.2.9 3 | djangorestframework>=3.13.1 4 | django-friendship>=1.9.4 5 | -------------------------------------------------------------------------------- /rest_friendship/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.2.1' 2 | -------------------------------------------------------------------------------- /rest_friendship/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class RestFriendshipConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'rest_friendship' 7 | verbose_name = 'Rest Friendship' 8 | -------------------------------------------------------------------------------- /rest_friendship/serializers.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import get_user_model 2 | from rest_framework import serializers 3 | from friendship.models import FriendshipRequest 4 | 5 | 6 | class FriendSerializer(serializers.ModelSerializer): 7 | class Meta: 8 | model = get_user_model() 9 | fields = ('id', 'username', 'email') 10 | 11 | 12 | class FriendshipRequestSerializer(serializers.ModelSerializer): 13 | to_user = serializers.CharField() 14 | from_user = serializers.StringRelatedField() 15 | 16 | class Meta: 17 | model = FriendshipRequest 18 | fields = ('id', 'from_user', 'to_user', 'message', 19 | 'created', 'rejected', 'viewed') 20 | extra_kwargs = { 21 | 'from_user': {'read_only': True}, 22 | 'created': {'read_only': True}, 23 | 'rejected': {'read_only': True}, 24 | 'viewed': {'read_only': True}, 25 | } 26 | 27 | 28 | class FriendshipRequestResponseSerializer(serializers.ModelSerializer): 29 | id = serializers.IntegerField() 30 | 31 | class Meta: 32 | model = FriendshipRequest 33 | fields = ('id',) 34 | -------------------------------------------------------------------------------- /rest_friendship/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path, include 2 | from rest_framework import routers 3 | from . import views 4 | 5 | router = routers.DefaultRouter() 6 | router.register('friends', views.FriendViewSet, 'friend') 7 | 8 | urlpatterns = [ 9 | path('', include(router.urls)), 10 | ] 11 | -------------------------------------------------------------------------------- /rest_friendship/views.py: -------------------------------------------------------------------------------- 1 | 2 | from django.conf import settings 3 | from django.contrib.auth import get_user_model 4 | from django.shortcuts import get_object_or_404 5 | from django.utils.module_loading import import_string 6 | from rest_framework import status, viewsets 7 | from rest_framework.decorators import action 8 | from rest_framework.response import Response 9 | from friendship.models import Friend, FriendshipRequest 10 | from friendship.exceptions import AlreadyExistsError, AlreadyFriendsError 11 | from .serializers import FriendshipRequestSerializer, FriendSerializer, FriendshipRequestResponseSerializer 12 | 13 | 14 | User = get_user_model() 15 | 16 | 17 | REST_FRIENDSHIP = getattr(settings, "REST_FRIENDSHIP", None) 18 | PERMISSION_CLASSES = [import_string(c) 19 | for c in REST_FRIENDSHIP["PERMISSION_CLASSES"]] 20 | USER_SERIALIZER = import_string(REST_FRIENDSHIP["USER_SERIALIZER"]) 21 | 22 | 23 | class FriendViewSet(viewsets.ModelViewSet): 24 | """ 25 | ViewSet for Friend model 26 | """ 27 | permission_classes = PERMISSION_CLASSES 28 | serializer_class = USER_SERIALIZER 29 | lookup_field = 'pk' 30 | 31 | def list(self, request): 32 | friend_requests = Friend.objects.friends(user=request.user) 33 | self.queryset = friend_requests 34 | self.http_method_names = ['get', 'head', 'options', ] 35 | return Response(FriendSerializer(friend_requests, many=True).data) 36 | 37 | def retrieve(self, request, pk=None): 38 | self.queryset = Friend.objects.friends(user=request.user) 39 | requested_user = get_object_or_404(User, pk=pk) 40 | if Friend.objects.are_friends(request.user, requested_user): 41 | self.http_method_names = ['get', 'head', 'options', ] 42 | return Response(FriendSerializer(requested_user, many=False).data) 43 | else: 44 | return Response( 45 | {'message': "Friend relationship not found for user."}, 46 | status.HTTP_400_BAD_REQUEST 47 | ) 48 | 49 | @ action(detail=False) 50 | def requests(self, request): 51 | friend_requests = Friend.objects.unrejected_requests(user=request.user) 52 | self.queryset = friend_requests 53 | return Response( 54 | FriendshipRequestSerializer(friend_requests, many=True).data) 55 | 56 | @ action(detail=False) 57 | def sent_requests(self, request): 58 | friend_requests = Friend.objects.sent_requests(user=request.user) 59 | self.queryset = friend_requests 60 | return Response( 61 | FriendshipRequestSerializer(friend_requests, many=True).data) 62 | 63 | @ action(detail=False) 64 | def rejected_requests(self, request): 65 | friend_requests = Friend.objects.rejected_requests(user=request.user) 66 | self.queryset = friend_requests 67 | return Response( 68 | FriendshipRequestSerializer(friend_requests, many=True).data) 69 | 70 | @ action(detail=False, 71 | serializer_class=FriendshipRequestSerializer, 72 | methods=['post']) 73 | def add_friend(self, request, username=None): 74 | """ 75 | Add a new friend with POST data 76 | - to_user 77 | - message 78 | """ 79 | # Creates a friend request from POST data: 80 | # - username 81 | # - message 82 | serializer = self.get_serializer(data=request.data) 83 | serializer.is_valid(raise_exception=True) 84 | to_user = get_object_or_404( 85 | User, 86 | username=serializer.validated_data.get('to_user') 87 | ) 88 | 89 | try: 90 | friend_obj = Friend.objects.add_friend( 91 | # The sender 92 | request.user, 93 | # The recipient 94 | to_user, 95 | # Message (...or empty str) 96 | message=request.data.get('message', '') 97 | ) 98 | return Response( 99 | FriendshipRequestSerializer(friend_obj).data, 100 | status.HTTP_201_CREATED 101 | ) 102 | except (AlreadyExistsError, AlreadyFriendsError) as e: 103 | return Response( 104 | {"message": str(e)}, 105 | status.HTTP_400_BAD_REQUEST 106 | ) 107 | 108 | @ action(detail=False, serializer_class=FriendshipRequestSerializer, methods=['post']) 109 | def remove_friend(self, request): 110 | """ 111 | Deletes a friend relationship. 112 | 113 | The username specified in the POST data will be 114 | removed from the current user's friends. 115 | """ 116 | serializer = self.get_serializer(data=request.data) 117 | serializer.is_valid(raise_exception=True) 118 | to_user = get_object_or_404( 119 | User, 120 | username=serializer.validated_data.get('to_user') 121 | ) 122 | 123 | if Friend.objects.remove_friend(request.user, to_user): 124 | message = 'Friend deleted.' 125 | status_code = status.HTTP_204_NO_CONTENT 126 | else: 127 | message = 'Friend not found.' 128 | status_code = status.HTTP_400_BAD_REQUEST 129 | 130 | return Response( 131 | {"message": message}, 132 | status=status_code 133 | ) 134 | 135 | @ action(detail=False, 136 | serializer_class=FriendshipRequestResponseSerializer, 137 | methods=['post']) 138 | def accept_request(self, request, id=None): 139 | """ 140 | Accepts a friend request 141 | 142 | The request id specified in the URL will be accepted 143 | """ 144 | id = request.data.get('id', None) 145 | friendship_request = get_object_or_404( 146 | FriendshipRequest, pk=id) 147 | 148 | if not friendship_request.to_user == request.user: 149 | return Response( 150 | {"message": "Request for current user not found."}, 151 | status.HTTP_400_BAD_REQUEST 152 | ) 153 | 154 | friendship_request.accept() 155 | return Response( 156 | {"message": "Request accepted, user added to friends."}, 157 | status.HTTP_201_CREATED 158 | ) 159 | 160 | @ action(detail=False, 161 | serializer_class=FriendshipRequestResponseSerializer, 162 | methods=['post']) 163 | def reject_request(self, request, id=None): 164 | """ 165 | Rejects a friend request 166 | 167 | The request id specified in the URL will be rejected 168 | """ 169 | id = request.data.get('id', None) 170 | friendship_request = get_object_or_404( 171 | FriendshipRequest, pk=id) 172 | if not friendship_request.to_user == request.user: 173 | return Response( 174 | {"message": "Request for current user not found."}, 175 | status.HTTP_400_BAD_REQUEST 176 | ) 177 | 178 | friendship_request.reject() 179 | 180 | return Response( 181 | { 182 | "message": "Request rejected, user NOT added to friends." 183 | }, 184 | status.HTTP_201_CREATED 185 | ) 186 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = django-rest-friendship 3 | package = rest_friendship 4 | version = attr: rest_friendship.__version__ 5 | url = https://github.com/dnmellen/django-rest-friendship 6 | author = Diego Navarro Mellén 7 | author_email = dnmellen@gmail.com 8 | description = DRF endpoints for django-friendship 9 | long_description = file: README.rst 10 | long_description_content_type = text/x-rst 11 | license = ISC License 12 | project_urls = 13 | Contributions = https://github.com/dnmellen/django-rest-friendship/graphs/contributors 14 | Issues = https://github.com/dnmellen/django-rest-friendship/issues 15 | classifiers = 16 | Development Status :: 3 - Alpha 17 | Intended Audience :: Developers 18 | Topic :: Internet :: WWW/HTTP 19 | License :: OSI Approved :: ISC License (ISCL) 20 | Framework :: Django :: 3.2 21 | Framework :: Django :: 4.0 22 | Programming Language :: Python :: 3.8 23 | Programming Language :: Python :: 3.9 24 | Programming Language :: Python :: 3.10 25 | Operating System :: OS Independent 26 | Natural Language :: English 27 | Environment :: Web Environment 28 | keywords = 29 | django 30 | django-friendship 31 | django-rest-friendship 32 | friends 33 | API 34 | restframework 35 | back-end 36 | webdev 37 | OOP 38 | 39 | [options] 40 | zip_safe = False 41 | packages = find: 42 | python_requires = >=3.8, <4 43 | install_requires = 44 | Django>=3.2.9<5 45 | djangorestframework==3.13.1 46 | django-friendship>=1.9.4 47 | 48 | [options.extras_require] 49 | test = 50 | coverage 51 | factory-boy 52 | flake8 53 | pytest 54 | pytest-django 55 | tox 56 | docs = 57 | mkdocs 58 | 59 | [wheel] 60 | universal = 1 61 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | 4 | setup( 5 | ) 6 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dnmellen/django-rest-friendship/0808b7ddd62b405b6b1ee7169011aca030b9ce70/tests/__init__.py -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | 3 | 4 | def pytest_configure(): 5 | 6 | settings.configure( 7 | 8 | REST_FRIENDSHIP={ 9 | 'PERMISSION_CLASSES': [ 10 | 'rest_framework.permissions.IsAuthenticated', 11 | ], 12 | 'USER_SERIALIZER': 'rest_friendship.serializers.FriendSerializer', 13 | }, 14 | DEBUG_PROPAGATE_EXCEPTIONS=True, 15 | DATABASES={'default': {'ENGINE': 'django.db.backends.sqlite3', 16 | 'NAME': ':memory:'}}, 17 | SITE_ID=1, 18 | SECRET_KEY='not very secret in tests', 19 | USE_I18N=True, 20 | STATIC_URL='/static/', 21 | ROOT_URLCONF='tests.urls', 22 | TEMPLATE_LOADERS=( 23 | 'django.template.loaders.filesystem.Loader', 24 | 'django.template.loaders.app_directories.Loader', 25 | ), 26 | MIDDLEWARE_CLASSES=( 27 | 'django.middleware.common.CommonMiddleware', 28 | 'django.contrib.sessions.middleware.SessionMiddleware', 29 | 'django.middleware.csrf.CsrfViewMiddleware', 30 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 31 | 'django.contrib.messages.middleware.MessageMiddleware', 32 | ), 33 | INSTALLED_APPS=( 34 | 'django.contrib.auth', 35 | 'django.contrib.contenttypes', 36 | 'django.contrib.sessions', 37 | 'django.contrib.sites', 38 | 'django.contrib.messages', 39 | 'django.contrib.staticfiles', 40 | 41 | 'rest_framework', 42 | 'rest_framework.authtoken', 43 | 'friendship', 44 | 'rest_friendship', 45 | 'tests', 46 | ), 47 | PASSWORD_HASHERS=( 48 | 'django.contrib.auth.hashers.SHA1PasswordHasher', 49 | 'django.contrib.auth.hashers.PBKDF2PasswordHasher', 50 | 'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher', 51 | 'django.contrib.auth.hashers.BCryptPasswordHasher', 52 | 'django.contrib.auth.hashers.MD5PasswordHasher', 53 | 'django.contrib.auth.hashers.CryptPasswordHasher', 54 | ), 55 | ) 56 | -------------------------------------------------------------------------------- /tests/factories.py: -------------------------------------------------------------------------------- 1 | import factory 2 | from django.contrib.auth import get_user_model 3 | 4 | User = get_user_model() 5 | 6 | 7 | class UserFactory(factory.django.DjangoModelFactory): 8 | username = factory.Sequence(lambda n: 'user-{0}'.format(n)) 9 | email = factory.Sequence(lambda n: 'user-{0}@example.com'.format(n)) 10 | password = factory.PostGenerationMethodCall('set_password', 'password') 11 | 12 | class Meta: 13 | model = User 14 | django_get_or_create = ('username', ) 15 | -------------------------------------------------------------------------------- /tests/models.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dnmellen/django-rest-friendship/0808b7ddd62b405b6b1ee7169011aca030b9ce70/tests/models.py -------------------------------------------------------------------------------- /tests/serializers.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dnmellen/django-rest-friendship/0808b7ddd62b405b6b1ee7169011aca030b9ce70/tests/serializers.py -------------------------------------------------------------------------------- /tests/test_views.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from rest_framework.test import APIClient 3 | from friendship.models import Friend, FriendshipRequest 4 | from .factories import UserFactory, User 5 | 6 | 7 | # from tests.serializers import UserTestSerializer 8 | # Add tests for serializers and settings import. 9 | @pytest.mark.django_db(transaction=True) 10 | def test_create_friend_request_without_message(): 11 | 12 | # Create users 13 | user1 = UserFactory() 14 | user2 = UserFactory() 15 | 16 | client = APIClient() 17 | client.force_authenticate(user=user1) 18 | data = {'to_user': user2.username} 19 | response = client.post('/friends/add_friend/', data=data) 20 | id = response.data['id'] 21 | 22 | assert User.objects.all().count() == 2 23 | assert response.status_code == 201 24 | assert response.data['from_user'] == user1.username 25 | assert response.data['to_user'] == user2.username 26 | assert response.data['message'] == '' 27 | assert FriendshipRequest.objects.filter(pk=id).count() == 1 28 | 29 | 30 | @pytest.mark.django_db(transaction=True) 31 | def test_list_friends(): 32 | 33 | # Create users 34 | user1 = UserFactory() 35 | user2 = UserFactory() 36 | user3 = UserFactory() 37 | 38 | Friend.objects.add_friend( 39 | user2, # The sender 40 | user1, # The recipient 41 | message='Hi! I would like to add you' 42 | ) 43 | 44 | Friend.objects.add_friend( 45 | user3, # The sender 46 | user1, # The recipient 47 | message='Hi! I would like to add you' 48 | ) 49 | 50 | for friend_request in FriendshipRequest.objects.filter(to_user=user1): 51 | friend_request.accept() 52 | 53 | client = APIClient() 54 | client.force_authenticate(user=user1) 55 | response = client.get('/friends/') 56 | 57 | assert User.objects.all().count() == 3 58 | assert response.status_code == 200 59 | assert len(response.data) == 2 60 | 61 | 62 | @pytest.mark.django_db(transaction=True) 63 | def test_detail_friend(): 64 | 65 | # Create users 66 | user1 = UserFactory() 67 | user2 = UserFactory() 68 | 69 | Friend.objects.add_friend( 70 | user2, # The sender 71 | user1, # The recipient 72 | message='Hi! I would like to add you' 73 | ) 74 | 75 | FriendshipRequest.objects.first().accept() 76 | 77 | client = APIClient() 78 | client.force_authenticate(user=user1) 79 | response = client.get('/friends/{}/'.format(user2.pk)) 80 | 81 | assert response.status_code == 200 82 | assert len(response.data) == 3 83 | assert response.data['id'] == user2.id 84 | assert response.data['username'] == user2.username 85 | assert response.data['email'] == user2.email 86 | 87 | 88 | @pytest.mark.django_db(transaction=True) 89 | def test_detail_not_your_friend(): 90 | 91 | # Create users 92 | user1 = UserFactory() 93 | user2 = UserFactory() 94 | user3 = UserFactory() 95 | 96 | Friend.objects.add_friend( 97 | user2, # The sender 98 | user1, # The recipient 99 | message='Hi! I would like to add you' 100 | ) 101 | 102 | FriendshipRequest.objects.first().accept() 103 | 104 | client = APIClient() 105 | client.force_authenticate(user=user1) 106 | response = client.get('/friends/{}/'.format(user3.pk)) 107 | 108 | assert response.status_code == 400 109 | assert len(response.data) == 1 110 | assert response.data['message'] == 'Friend relationship not found for user.' 111 | 112 | 113 | @pytest.mark.django_db(transaction=True) 114 | def test_create_friend_request(): 115 | 116 | # Create users 117 | user1 = UserFactory() 118 | user2 = UserFactory() 119 | 120 | client = APIClient() 121 | client.force_authenticate(user=user1) 122 | data = {'to_user': user2.username, 'message': 'Hi there!'} 123 | response = client.post('/friends/add_friend/', data=data) 124 | id = response.data['id'] 125 | 126 | assert User.objects.all().count() == 2 127 | assert response.status_code == 201 128 | assert response.data['from_user'] == user1.username 129 | assert response.data['to_user'] == user2.username 130 | assert response.data['message'] == 'Hi there!' 131 | assert FriendshipRequest.objects.filter(pk=id).count() == 1 132 | 133 | 134 | @pytest.mark.django_db(transaction=True) 135 | def test_create_duplicate_friend_request(): 136 | 137 | # Create users 138 | user1 = UserFactory() 139 | user2 = UserFactory() 140 | 141 | client = APIClient() 142 | client.force_authenticate(user=user1) 143 | data = {'to_user': user2.username, 'message': 'Hi there!'} 144 | client.post('/friends/add_friend/', data=data) 145 | response = client.post('/friends/add_friend/', data=data) 146 | 147 | assert FriendshipRequest.objects.all().count() == 1 148 | assert response.status_code == 400 149 | assert response.data['message'] == 'You already requested friendship from this user.' 150 | 151 | 152 | @pytest.mark.django_db(transaction=True) 153 | def test_create_existing_friend(): 154 | 155 | # Create users 156 | user1 = UserFactory() 157 | user2 = UserFactory() 158 | 159 | client = APIClient() 160 | client.force_authenticate(user=user1) 161 | data = {'to_user': user2.username, 'message': 'Hi there!'} 162 | client.post('/friends/add_friend/', data=data) 163 | FriendshipRequest.objects.first().accept() 164 | response = client.post('/friends/add_friend/', data=data) 165 | 166 | assert Friend.objects.all().count() == 2 167 | assert response.status_code == 400 168 | assert response.data['message'] == 'Users are already friends' 169 | 170 | 171 | @pytest.mark.django_db(transaction=True) 172 | def test_create_friend_request_user_not_found(): 173 | 174 | # Create users 175 | user1 = UserFactory() 176 | 177 | client = APIClient() 178 | client.force_authenticate(user=user1) 179 | 180 | data = {'to_user': 'accountdoesntexist', 'message': 'Hi there!'} 181 | response = client.post('/friends/add_friend/', data=data) 182 | 183 | assert response.status_code == 404 184 | assert FriendshipRequest.objects.all().count() == 0 185 | assert response.data['detail'] == 'Not found.' 186 | 187 | 188 | @pytest.mark.django_db(transaction=True) 189 | def test_create_friend_request_unauthenticated(): 190 | 191 | # Create users 192 | user2 = UserFactory() 193 | 194 | client = APIClient() 195 | data = {'to_user': user2.username, 'message': 'Hi there!'} 196 | response = client.post('/friends/add_friend/', data=data) 197 | assert response.status_code == 403 198 | 199 | 200 | @pytest.mark.django_db(transaction=True) 201 | def test_list_friend_requests(): 202 | 203 | # Create users 204 | user1 = UserFactory() 205 | user2 = UserFactory() 206 | user3 = UserFactory() 207 | 208 | Friend.objects.add_friend( 209 | user2, # The sender 210 | user1, # The recipient 211 | message='Hi! I would like to add you' 212 | ) 213 | 214 | Friend.objects.add_friend( 215 | user3, # The sender 216 | user1, # The recipient 217 | message='Hi! I would like to add you' 218 | ) 219 | 220 | client = APIClient() 221 | client.force_authenticate(user=user1) 222 | response = client.get('/friends/requests/') 223 | 224 | assert response.status_code == 200 225 | assert len(response.data) == 2 226 | assert response.data[0]['from_user'] == user2.username 227 | assert response.data[1]['from_user'] == user3.username 228 | 229 | 230 | @pytest.mark.django_db(transaction=True) 231 | def test_list_sent_friend_requests(): 232 | 233 | # Create users 234 | user1 = UserFactory() 235 | user2 = UserFactory() 236 | user3 = UserFactory() 237 | 238 | Friend.objects.add_friend( 239 | user2, # The sender 240 | user1, # The recipient 241 | message='Hi! I would like to add you' 242 | ) 243 | 244 | Friend.objects.add_friend( 245 | user2, # The sender 246 | user3, # The recipient 247 | message='Hi! I would like to add you' 248 | ) 249 | 250 | client = APIClient() 251 | client.force_authenticate(user=user2) 252 | response = client.get('/friends/sent_requests/') 253 | 254 | assert response.status_code == 200 255 | assert len(response.data) == 2 256 | assert response.data[0]['to_user'] == user1.username 257 | assert response.data[1]['to_user'] == user3.username 258 | 259 | 260 | @pytest.mark.django_db(transaction=True) 261 | def test_list_rejected_friend_requests(): 262 | 263 | # Create users 264 | user1 = UserFactory() 265 | user2 = UserFactory() 266 | user3 = UserFactory() 267 | 268 | Friend.objects.add_friend( 269 | user2, # The sender 270 | user1, # The recipient 271 | message='Hi! I would like to add you' 272 | ) 273 | 274 | Friend.objects.add_friend( 275 | user3, # The sender 276 | user1, # The recipient 277 | message='Hi! I would like to add you' 278 | ) 279 | 280 | for friend_request in FriendshipRequest.objects.filter(to_user=user1): 281 | friend_request.reject() 282 | 283 | client = APIClient() 284 | client.force_authenticate(user=user1) 285 | response = client.get('/friends/rejected_requests/') 286 | 287 | assert response.status_code == 200 288 | assert len(response.data) == 2 289 | assert response.data[0]['to_user'] == user1.username 290 | 291 | 292 | @pytest.mark.django_db(transaction=True) 293 | def test_accept_friend_request(): 294 | 295 | # Create users 296 | user1 = UserFactory() 297 | user2 = UserFactory() 298 | 299 | Friend.objects.add_friend( 300 | user2, # The sender 301 | user1, # The recipient 302 | message='Hi! I would like to add you' 303 | ) 304 | 305 | fr = FriendshipRequest.objects.filter(to_user=user1).first() 306 | 307 | client = APIClient() 308 | client.force_authenticate(user=user1) 309 | response = client.post('/friends/accept_request/', data={'id': fr.id}) 310 | 311 | assert FriendshipRequest.objects.all().count() == 0 312 | assert response.status_code == 201 313 | assert Friend.objects.are_friends(user1, user2) 314 | 315 | 316 | @pytest.mark.django_db(transaction=True) 317 | def test_accept_friend_request_not_found(): 318 | 319 | # Create users 320 | user1 = UserFactory() 321 | user2 = UserFactory() 322 | 323 | Friend.objects.add_friend( 324 | user2, # The sender 325 | user1, # The recipient 326 | message='Hi! I would like to add you' 327 | ) 328 | 329 | fr = FriendshipRequest.objects.filter(to_user=user1).first() 330 | 331 | client = APIClient() 332 | client.force_authenticate(user=user1) 333 | client.post('/friends/accept_request/', data={'id': fr.id}) 334 | 335 | # Post to the same url to confirm friend added and request deleted. 336 | response = client.post('/friends/accept_request/', data={'id': fr.id}) 337 | 338 | assert Friend.objects.are_friends(user1, user2) 339 | assert response.status_code == 404 340 | assert response.data['detail'] == 'Not found.' 341 | 342 | 343 | @pytest.mark.django_db(transaction=True) 344 | def test_accept_friend_request_of_other_user(): 345 | 346 | # Create users 347 | user1 = UserFactory() 348 | user2 = UserFactory() 349 | 350 | Friend.objects.add_friend( 351 | user2, # The sender 352 | user1, # The recipient 353 | message='Hi! I would like to add you' 354 | ) 355 | 356 | fr = FriendshipRequest.objects.filter(to_user=user1).first() 357 | 358 | client = APIClient() 359 | client.force_authenticate(user=user2) 360 | response = client.post('/friends/accept_request/', data={'id': fr.id}) 361 | 362 | assert response.status_code == 400 363 | assert not Friend.objects.are_friends(user1, user2) 364 | 365 | 366 | @pytest.mark.django_db(transaction=True) 367 | def test_reject_friend_request(): 368 | 369 | # Create users 370 | user1 = UserFactory() 371 | user2 = UserFactory() 372 | 373 | Friend.objects.add_friend( 374 | user2, # The sender 375 | user1, # The recipient 376 | message='Hi! I would like to add you' 377 | ) 378 | 379 | fr = FriendshipRequest.objects.filter(to_user=user1).first() 380 | 381 | client = APIClient() 382 | client.force_authenticate(user=user1) 383 | response = client.post('/friends/reject_request/', data={'id': fr.id}) 384 | 385 | assert response.status_code == 201 386 | assert not Friend.objects.are_friends(user1, user2) 387 | 388 | 389 | @pytest.mark.django_db(transaction=True) 390 | def test_reject_friend_request_of_other_user(): 391 | 392 | # Create users 393 | user1 = UserFactory() 394 | user2 = UserFactory() 395 | 396 | Friend.objects.add_friend( 397 | user2, # The sender 398 | user1, # The recipient 399 | message='Hi! I would like to add you' 400 | ) 401 | 402 | fr = FriendshipRequest.objects.filter(to_user=user1).first() 403 | 404 | client = APIClient() 405 | client.force_authenticate(user=user2) 406 | response = client.post('/friends/reject_request/', data={'id': fr.id}) 407 | 408 | assert response.status_code == 400 409 | assert not Friend.objects.are_friends(user1, user2) 410 | 411 | 412 | @pytest.mark.django_db(transaction=True) 413 | def test_delete_friend(): 414 | 415 | # Create users 416 | user1 = UserFactory() 417 | user2 = UserFactory() 418 | user3 = UserFactory() 419 | 420 | Friend.objects.add_friend( 421 | user2, # The sender 422 | user1, # The recipient 423 | message='Hi! I would like to add you' 424 | ) 425 | 426 | Friend.objects.add_friend( 427 | user3, # The sender 428 | user1, # The recipient 429 | message='Hi! I would like to add you' 430 | ) 431 | 432 | for friend_request in FriendshipRequest.objects.filter(to_user=user1): 433 | friend_request.accept() 434 | 435 | client = APIClient() 436 | client.force_authenticate(user=user1) 437 | response = client.post( 438 | '/friends/remove_friend/', 439 | data={'to_user': user2.username} 440 | ) 441 | 442 | assert not Friend.objects.are_friends(user1, user2) 443 | assert Friend.objects.are_friends(user1, user3) 444 | assert response.status_code == 204 445 | assert response.data['message'] == 'Friend deleted.' 446 | 447 | 448 | @pytest.mark.django_db(transaction=True) 449 | def test_delete_friend_not_your_friend(): 450 | 451 | # Create users 452 | user1 = UserFactory() 453 | user2 = UserFactory() 454 | user3 = UserFactory() 455 | 456 | Friend.objects.add_friend( 457 | user2, # The sender 458 | user1, # The recipient 459 | message='Hi! I would like to add you' 460 | ) 461 | 462 | FriendshipRequest.objects.first().accept() 463 | 464 | client = APIClient() 465 | client.force_authenticate(user=user1) 466 | response = client.post( 467 | '/friends/remove_friend/', 468 | data={'to_user': user3.username} 469 | ) 470 | 471 | assert not Friend.objects.are_friends(user1, user3) 472 | assert response.status_code == 400 473 | assert response.data['message'] == 'Friend not found.' 474 | 475 | 476 | @pytest.mark.django_db(transaction=True) 477 | def test_delete_friend_does_not_exist(): 478 | # Create users 479 | user1 = UserFactory() 480 | user2 = UserFactory() 481 | 482 | Friend.objects.add_friend( 483 | user2, # The sender 484 | user1, # The recipient 485 | message='Hi! I would like to add you' 486 | ) 487 | 488 | FriendshipRequest.objects.first().accept() 489 | 490 | client = APIClient() 491 | client.force_authenticate(user=user1) 492 | response = client.post( 493 | '/friends/remove_friend/', 494 | data={'to_user': 'doesnotexist'} 495 | ) 496 | 497 | assert response.data['detail'] == 'Not found.' 498 | assert response.status_code == 404 499 | -------------------------------------------------------------------------------- /tests/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path, include 2 | 3 | urlpatterns = [ 4 | path('', include(('rest_friendship.urls', 'rest_friendship'), namespace='rest_friendship')), 5 | ] 6 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = 3 | py{38,39,310}-django{32,40} 4 | flake8 5 | report 6 | clean 7 | 8 | [gh-actions] 9 | python = 10 | 3.8: py38 11 | 3.9: py39 12 | 3.10: py310,docs,report, 13 | 14 | [pytest] 15 | testpaths = tests rest_friendship 16 | norecursedirs= .git .github .tox .vscode env build dist docs tmp* 17 | 18 | [flake8] 19 | max-line-length = 90 20 | ignore = E501,E9,F63,F7,F82 21 | max-complexity = 12 22 | exclude = .git,.github,.tox,.vscode,__pycache__,.pytest_cache,docs,old,build,dist,env,htmlcov,*.xml,.coverage,.eggs,*.egg 23 | 24 | [tool:pytest] 25 | addopts = -vv 26 | 27 | [tool:coverage] 28 | addopts = 29 | --cov-report=term-missing 30 | 31 | [testenv] 32 | commands = coverage run --append -m pytest 33 | deps = 34 | django32: Django==3.2 35 | django40: Django==4.0.1 36 | djangorestframework==3.13.1 37 | django-friendship>=1.9.3 38 | factory-boy>=2.8.1 39 | pytest-django>=4.5.2 40 | pytest 41 | coverage 42 | 43 | depends = 44 | report: py38,py39,py310-django32 45 | clean: report 46 | 47 | [testenv:report] 48 | skip_install = true 49 | deps = coverage 50 | commands = 51 | coverage report 52 | coverage xml 53 | 54 | 55 | [testenv:clean] 56 | skip_install = true 57 | deps = coverage 58 | commands = 59 | coverage erase 60 | 61 | [testenv:flake8] 62 | skip_install = false 63 | deps = flake8 64 | commands = flake8 65 | --------------------------------------------------------------------------------