Commit 0e4159a1 authored by Marc Feger's avatar Marc Feger
Browse files

Merge branch 'feedingChickenWithChicken' into 'main'

Feeding chicken with chicken

See merge request !3
parents 6a07c7fb 40699be5
Pipeline #76061 passed with stages
in 4 minutes and 9 seconds
# The Social Network
The package "The Social Network" is a django base backend core element for any possible social network you can think of.
The package "The Social Network" is a django base backend core element for any possible social network you can think of. You can easily create clones for all popular social networks.
It contains the following models
## Installation
Minimum requierments are:
Django.Authentication.User
> [Python](https://www.python.org/downloads/) >= 3.9
> [Django](https://pypi.org/project/Django/) >= 3.2.9
> [Pillow](https://pypi.org/project/Pillow/) >= 8.4.0
> [djangorestframework](https://pypi.org/project/djangorestframework/) >= 3.12.4
Intallation can be done by **pip** like
> pip install the-social-network
or download manuel on [Pypi](https://pypi.org/project/the-social-network/).
## How to use
If you not have already created a django python project, [create](https://docs.djangoproject.com/en/3.2/intro/tutorial01/) it at first in a new directory with the command
> django-admin startproject mysite
This will create a **mysite** directory in your current directory.
Open the **mysite** directory and open the **settings.py**.
Add *'the_social_network'* to **INSTALLED_APPS** and save the file.
Next open the urls.py and add the following lines to your **urlpatterns**
> url(r'^authentication/', include('the_social_network.urls.authenticationUrls')),
> url(r'^accounts/', include('the_social_network.urls.accountUrls')),
> url(r'^search/', include('the_social_network.urls.searchUrls')),
> url(r'^contents/', include('the_social_network.urls.contentUrls'))
Now everything is ready to run django with **the-social-network**.
Create the database with
> python manage.py migrate
and start the server with
> python manage.py runserver
the default django information page should showup if you open [http://127.0.0.1:8000/](http://127.0.0.1:8000/) in your browser.
## Request API for the URLs
In all requests (except for the request of *authentication/register/* or */authentication/login/*) you need to send the authentification token inside the header.
For authorization use the header name "Authorization" and the value "Token <token>"
#### Authentication
##### POST url: ".../authentication/register/"
Register a user
Requestbody:
```json
{
"username": "username",
"password": "password",
"email": "email"
}
```
Responsebody:
```json
{
"token": "token"
}
```
##### POST url: ".../authentication/login/"
Login a user
Requestbody:
```json
{
"username": "username",
"password": "password"
}
```
Responsebody:
```json
{
"token": "token"
}
```
##### POST url: ".../authentication/logout/"
Logout a user
Requestbody: None
Responsebody: None
Success: HTTP/200
##### GET url: ".../authentication/validate/"
Validate a token
Requestbody: None
Responsebody: None
Success: HTTP/200
#### Account
##### GET url: ".../accounts/show/<user_id>/"
Show a public user
Requestbody: None
Responsebody:
```json
[{
"user": {
"id": ...,
"username": ...,
"email": ...,
"date_joined": "..."
},
"image": "...",
"biography": "...",
"related_by": [],
"related_to": [],
"statements": []
}]
```
##### GET url: ".../accounts/show/own/"
Show the own user
Requestbody: None
Responsebody:
```json
[{
"user": {
"id": ...,
"username": ...,
"email": ...,
"date_joined": "..."
},
"image": "...",
"biography": "...",
"related_by": [],
"related_to": [],
"statements": []
}]
```
##### GET url: ".../accounts/show/all/"
Show all public users
Requestbody: None
Responsebody:
```json
[{
"user": {
"id": ...,
"username": ...,
"email": ...,
"date_joined": "..."
},
"image": "...",
"biography": "...",
"related_by": [],
"related_to": [],
"statements": []
},
...
]
```
##### PUT url: ".../accounts/update/"
Updates the own account. Only "Biography" and "Image" are allowed to be updated.
Requestbody:
```json
{
"biography": "...",
"file": "..."
}
```
Responsebody: None
Success: HTTP/200
##### PUT url: ".../accounts/follow/<user_id>/"
Follow a user
Requestbody: None
Responsebody: None
Success: HTTP/200
##### PUT url: ".../accounts/unfollow/<user_id>/"
Unfollow a user
Requestbody: None
Responsebody: None
Success: HTTP/200
##### PUT url: ".../accounts/operation/add/statement/"
Add a statement to the own account
Requestbody:
```json
{
"input": "<statement>"
"reactions": { "to": <reaction_to_a_statement_id>, "relation": <"attack" or "support">} <--- optional
}
```
Responsebody:
```json
{
"id": ...,
"author": {
"user": {
"id": ...,
"username": "..."
},
"image": "..."
},
"content": "...",
"tagged": [],
"mentioned": [],
"created": "...",
"relation_to_parent": ...
}
```
Success: HTTP/200
#### Contents
##### GET url: ".../contents/statements/get/<statement_id>/"
Get a statement
Requestbody: None
Responsebody:
```json
[
{
"id": ...,
"author": {
"user": {
"id": ...,
"username": "..."
},
"image": "..."
},
"content": "...",
"tagged": [],
"mentioned": [],
"created": "...",
"relation_to_parent": ...,
"reactions": []
}
]
```
##### GET url: ".../contents/statements/with/hashtag/"
Get all statements with a hashtag
Requestbody: None
Queryparameters: "?q=<hashtag>"
Responsebody:
```json
[
{
"id": ...,
"author": {
"user": {
"id": ...,
"username": "..."
},
"image": "..."
},
"content": "...",
"tagged": [],
"mentioned": [],
"created": "...",
"relation_to_parent": ...,
"reactions": []
},
...
]
```
##### GET url: ".../contents/statements/feed/"
Get all statements of the accounts that are followed by the user
Requestbody: None
Responsebody:
```json
[
{
"id": ...,
"author": {
"user": {
"id": ...,
"username": "..."
},
"image": "..."
},
"content": "...",
"tagged": [],
"mentioned": [],
"created": "...",
"relation_to_parent": ...,
"reactions": []
},
...
]
```
##### GET url: ".../contents/statements/feed/pagination"
Get statements of the accounts that are followed by the user
Requestbody: None
Queryparameters: "?page=<page_number>&size=<number_of_statements_per_page>"
Responsebody:
```json
{
"total": ...,
"data": [
{
"id": ...,
"author": {
"user": {
"id": ...,
"username": "..."
},
"image": "..."
},
"content": "...",
"tagged": [],
"mentioned": [],
"created": "...",
"relation_to_parent": ...,
"reactions": []
},
...
]
}
```
##### GET url: ".../contents/trending/hashtag/"
Get all trending hashtags which are most used in statements
Requestbody: None
Reponsebody: ++++++ TODO: Setting of a hashtag not clear ++++++
#### Search
##### GET url: ".../search/"
Searchs for a user or hashtag
Requestbody: None
Queryparameters: "?q=<search_query>&filter=<"user" or "hashtag">"
Responsebody:
```json
{
"accounts": [
{
"user": {
"id": ...,
"username": "...",
},
"image": "..."
},
...
],
"hashtags": [
{
"id": ...,
"tag": "..."
},
...
]
}
```
## Core Database structure
The project requieres the base authentication database structure from django and extends it with the following tables:
#### the_social_network_account
with
user_id: int as primary key and foreign key to django auth_user
image: varchar(100)
biography: varchar(1000)
#### the_social_network_statement
with
id: int as primary key
author_id: int as foreign key to the_social_network_account
content: varchar(120)
created: datetime
#### the_social_network_accounttagging
with
id: int as primary key
created: datetime
account_id: int as foreign key to the_social_network_account
statement_id: int as foreign key to the_social_network_statement
#### the_social_network_hashtag
with
id: int as primary key
tag: varchar(30)
created: datetime
#### the_social_network_hashtagtagging
with
id: int as primary key
created: datetime
hashtag_id: int as foreign key to the_social_network_hashtag
statement_id: int as foreign key to the_social_network_statement
#### the_social_network_reaction
with
id: int as primary key
created: datetime
vote: small uint
child_id: int as foreign key to the_social_network_statement
parent_id: int as foreign key to the_social_network_statement
#### the_social_network_relationship
with
id: int as primary key
created: datetime
from_account_id: int as foreign key to the_social_network_account
to_account_id: int as foreign key to the_social_network_account
[tool.poetry]
name = "the-social-network"
version = "0.0.3"
version = "0.0.4"
description = "Basic social network core."
authors = ["Marc Feger <marc.feger@hhu.de>"]
license = "BSD-4"
......
......@@ -616,6 +616,45 @@ class TestGetStatementFeed(APITestCase):
self.assertTrue(result[1].get("id") > result[2].get("id"))
self.assertTrue(result[1].get("created") > result[2].get("created"))
def test_feed_contains_correct_data_pagination(self):
self.client.credentials(HTTP_AUTHORIZATION='Token ' + str(self.token_beate))
response: Response = self.client.get(path="/contents/statements/feed/pagination/?page=1&size=3")
result = response.data["data"]
result_total = response.data["total"]
self.assertTrue(len(result), 3)
self.assertTrue(result_total, 3)
self.assertEqual(result[0].get("id"), self.statement_3.id)
self.assertEqual(result[1].get("id"), self.statement_2.id)
self.assertEqual(result[2].get("id"), self.statement_1.id)
self.assertTrue(result[0].get("id") > result[1].get("id"))
self.assertTrue(result[0].get("created") > result[1].get("created"))
self.assertTrue(result[1].get("id") > result[2].get("id"))
self.assertTrue(result[1].get("created") > result[2].get("created"))
def test_feed_load_over_size_pagination(self):
self.client.credentials(HTTP_AUTHORIZATION='Token ' + str(self.token_beate))
response: Response = self.client.get(path="/contents/statements/feed/pagination/?page=1&size=4")
result = response.data["data"]
result_total = response.data["total"]
self.assertTrue(len(result), 3)
self.assertTrue(result_total, 3)
def test_feed_load_under_size_pagination(self):
self.client.credentials(HTTP_AUTHORIZATION='Token ' + str(self.token_beate))
response: Response = self.client.get(path="/contents/statements/feed/pagination/?page=1&size=2")
result = response.data["data"]
result_total = response.data["total"]
self.assertTrue(len(result), 2)
self.assertTrue(result_total, 3)
def test_feed_contains_no_data_pagination(self):
self.client.credentials(HTTP_AUTHORIZATION='Token ' + str(self.token_beate))
response: Response = self.client.get(path="/contents/statements/feed/pagination/?page=2&size=2")
result = response.data["data"]
result_total = response.data["total"]
self.assertTrue(len(result), 0)
self.assertTrue(result_total, 3)
class TestTrendingHashtag(APITestCase):
def setUp(self):
......
......@@ -6,5 +6,6 @@ urlpatterns = [
path('statements/get/<int:id>/', ShowStatement.as_view(), name="show_statement"),
path('statements/with/hashtag/', ShowStatementsWithHashtag.as_view(), name="show_statement_with_hashtag"),
path('statements/feed/', ShowStatementFeed.as_view(), name="show_statement_feed"),
path('statements/feed/pagination/', ShowStatementFeedPagination.as_view(), name="show_statement_feed_pagination"),
path('trending/hashtag/', ShowTrendingHashtag.as_view(), name="show_trending_hashtags"),
]
\ No newline at end of file
......@@ -82,7 +82,6 @@ class ShowStatementFeed(APIView):
def get(request: Request):
"""
This is for getting the feed.
Todo: Add pagination for infinite scrolling.
:param request: The request containing the token to identify the calling user.
:return: Feed for the calling user based on the actions of those the calling account follows.
"""
......@@ -93,6 +92,47 @@ class ShowStatementFeed(APIView):
return Response(status=status.HTTP_200_OK, data=serializer.data)
class ShowStatementFeedPagination(APIView):
"""
This view is for querying the feed for an calling account.
The feed is generated by the accounts the calling account is following.
To get the feed the calling account must be authenticated.
"""
authentication_classes = [TokenAuthentication]
permission_classes = (IsAuthenticated,)
@staticmethod
def get(request: Request):
"""
This is for getting the feed with pagination.
Todo: Add pagination for infinite scrolling.
:param request: The request containing the token to identify the calling user. The page number. The size number.
:return: Feed for the calling user based on the actions of those the calling account follows.
"""
page: int = int(request.query_params.get('page', None))
size: int = int(request.query_params.get('size', None))
if not page or not size or page <= 0 or size <= 0:
return Response(status=status.HTTP_400_BAD_REQUEST)
account: Account = Account.objects.get(user=request.user)
following: List[Account] = account.get_related_to() + [account]
feed: QuerySet[Statement] = Statement.objects.filter(author__in=following)
first = (page - 1) * size
last = first + size
total: int = feed.count()
if first >= total:
feed = []
else:
if last >= total:
last = None
feed = feed[first:last]
serializer: StatementSerializer = StatementSerializer(instance=feed, many=True)
return Response(status=status.HTTP_200_OK, data={"data": serializer.data, "total": total})
class ShowTrendingHashtag(APIView):
"""
This view is for getting the five most trending hashtags.
......@@ -114,9 +154,7 @@ class ShowTrendingHashtag(APIView):
:return: 200 OK with empty or not empty data section. The data section is empty if there are not hashtags.
"""
hashtags: QuerySet[Dict] = HashtagTagging.objects.values('hashtag')
hashtags_counted: QuerySet[Dict] = hashtags.annotate(
the_count=Count('hashtag')
).order_by("-the_count")
hashtags_counted: QuerySet[Dict] = hashtags.annotate(the_count=Count('hashtag')).order_by("-the_count")
counted: Dict = {item["hashtag"]: item["the_count"] for item in hashtags_counted}
hashtags: QuerySet[Hashtag] = Hashtag.objects.filter(id__in=counted.keys())
if not hashtags_counted:
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment