Skip to content
Snippets Groups Projects
Commit c86e24da authored by Marc Feger's avatar Marc Feger
Browse files

Add first implementation for the-social-network core

parent e390fd69
Branches
No related tags found
No related merge requests found
Showing
with 2118 additions and 0 deletions
.idea/
.DS_Store
*.sqlite3
__pycache__
# dependencies
*/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
*/build
# misc
.env.local
.env.development.local
.env.test.local
.env.production.local
.env
npm-debug.log*
yarn-debug.log*
yarn-error.log*
*.eslintcache
*/media/account/images/
solar/django_static
pypi/*/
......@@ -4,6 +4,11 @@ verify_ssl = true
name = "pypi"
[packages]
django = "*"
django-cors-headers = "*"
djangorestframework = "*"
pillow = "*"
uwsgi = "*"
[dev-packages]
......
{
"_meta": {
"hash": {
"sha256": "f1ffc1e60d515d604136eec07eea7e16c697c4e5529414b3b41eb9a672871223"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.9"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"asgiref": {
"hashes": [
"sha256:4ef1ab46b484e3c706329cedeff284a5d40824200638503f5768edb6de7d58e9",
"sha256:ffc141aa908e6f175673e7b1b3b7af4fdb0ecb738fc5c8b88f69f055c2415214"
],
"markers": "python_version >= '3.6'",
"version": "==3.4.1"
},
"django": {
"hashes": [
"sha256:51284300f1522ffcdb07ccbdf676a307c6678659e1284f0618e5a774127a6a08",
"sha256:e22c9266da3eec7827737cde57694d7db801fedac938d252bf27377cec06ed1b"
],
"index": "pypi",
"version": "==3.2.9"
},
"django-cors-headers": {
"hashes": [
"sha256:cba6e99659abb0e47cc4aaabb8fcde03f193e6bb3b92ba47c5185ec4cedc5d9e",
"sha256:cd6f4360f5246569c149dc1c40c907c191f1ec45551e10d2a2e2e68512652f78"
],
"index": "pypi",
"version": "==3.10.0"
},
"djangorestframework": {
"hashes": [
"sha256:6d1d59f623a5ad0509fe0d6bfe93cbdfe17b8116ebc8eda86d45f6e16e819aaf",
"sha256:f747949a8ddac876e879190df194b925c177cdeb725a099db1460872f7c0a7f2"
],
"index": "pypi",
"version": "==3.12.4"
},
"pillow": {
"hashes": [
"sha256:066f3999cb3b070a95c3652712cffa1a748cd02d60ad7b4e485c3748a04d9d76",
"sha256:0a0956fdc5defc34462bb1c765ee88d933239f9a94bc37d132004775241a7585",
"sha256:0b052a619a8bfcf26bd8b3f48f45283f9e977890263e4571f2393ed8898d331b",
"sha256:1394a6ad5abc838c5cd8a92c5a07535648cdf6d09e8e2d6df916dfa9ea86ead8",
"sha256:1bc723b434fbc4ab50bb68e11e93ce5fb69866ad621e3c2c9bdb0cd70e345f55",
"sha256:244cf3b97802c34c41905d22810846802a3329ddcb93ccc432870243211c79fc",
"sha256:25a49dc2e2f74e65efaa32b153527fc5ac98508d502fa46e74fa4fd678ed6645",
"sha256:2e4440b8f00f504ee4b53fe30f4e381aae30b0568193be305256b1462216feff",
"sha256:3862b7256046fcd950618ed22d1d60b842e3a40a48236a5498746f21189afbbc",
"sha256:3eb1ce5f65908556c2d8685a8f0a6e989d887ec4057326f6c22b24e8a172c66b",
"sha256:3f97cfb1e5a392d75dd8b9fd274d205404729923840ca94ca45a0af57e13dbe6",
"sha256:493cb4e415f44cd601fcec11c99836f707bb714ab03f5ed46ac25713baf0ff20",
"sha256:4acc0985ddf39d1bc969a9220b51d94ed51695d455c228d8ac29fcdb25810e6e",
"sha256:5503c86916d27c2e101b7f71c2ae2cddba01a2cf55b8395b0255fd33fa4d1f1a",
"sha256:5b7bb9de00197fb4261825c15551adf7605cf14a80badf1761d61e59da347779",
"sha256:5e9ac5f66616b87d4da618a20ab0a38324dbe88d8a39b55be8964eb520021e02",
"sha256:620582db2a85b2df5f8a82ddeb52116560d7e5e6b055095f04ad828d1b0baa39",
"sha256:62cc1afda735a8d109007164714e73771b499768b9bb5afcbbee9d0ff374b43f",
"sha256:70ad9e5c6cb9b8487280a02c0ad8a51581dcbbe8484ce058477692a27c151c0a",
"sha256:72b9e656e340447f827885b8d7a15fc8c4e68d410dc2297ef6787eec0f0ea409",
"sha256:72cbcfd54df6caf85cc35264c77ede902452d6df41166010262374155947460c",
"sha256:792e5c12376594bfcb986ebf3855aa4b7c225754e9a9521298e460e92fb4a488",
"sha256:7b7017b61bbcdd7f6363aeceb881e23c46583739cb69a3ab39cb384f6ec82e5b",
"sha256:81f8d5c81e483a9442d72d182e1fb6dcb9723f289a57e8030811bac9ea3fef8d",
"sha256:82aafa8d5eb68c8463b6e9baeb4f19043bb31fefc03eb7b216b51e6a9981ae09",
"sha256:84c471a734240653a0ec91dec0996696eea227eafe72a33bd06c92697728046b",
"sha256:8c803ac3c28bbc53763e6825746f05cc407b20e4a69d0122e526a582e3b5e153",
"sha256:93ce9e955cc95959df98505e4608ad98281fff037350d8c2671c9aa86bcf10a9",
"sha256:9a3e5ddc44c14042f0844b8cf7d2cd455f6cc80fd7f5eefbe657292cf601d9ad",
"sha256:a4901622493f88b1a29bd30ec1a2f683782e57c3c16a2dbc7f2595ba01f639df",
"sha256:a5a4532a12314149d8b4e4ad8ff09dde7427731fcfa5917ff16d0291f13609df",
"sha256:b8831cb7332eda5dc89b21a7bce7ef6ad305548820595033a4b03cf3091235ed",
"sha256:b8e2f83c56e141920c39464b852de3719dfbfb6e3c99a2d8da0edf4fb33176ed",
"sha256:c70e94281588ef053ae8998039610dbd71bc509e4acbc77ab59d7d2937b10698",
"sha256:c8a17b5d948f4ceeceb66384727dde11b240736fddeda54ca740b9b8b1556b29",
"sha256:d82cdb63100ef5eedb8391732375e6d05993b765f72cb34311fab92103314649",
"sha256:d89363f02658e253dbd171f7c3716a5d340a24ee82d38aab9183f7fdf0cdca49",
"sha256:d99ec152570e4196772e7a8e4ba5320d2d27bf22fdf11743dd882936ed64305b",
"sha256:ddc4d832a0f0b4c52fff973a0d44b6c99839a9d016fe4e6a1cb8f3eea96479c2",
"sha256:e3dacecfbeec9a33e932f00c6cd7996e62f53ad46fbe677577394aaa90ee419a",
"sha256:eb9fc393f3c61f9054e1ed26e6fe912c7321af2f41ff49d3f83d05bacf22cc78"
],
"index": "pypi",
"version": "==8.4.0"
},
"pytz": {
"hashes": [
"sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c",
"sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326"
],
"version": "==2021.3"
},
"sqlparse": {
"hashes": [
"sha256:0c00730c74263a94e5a9919ade150dfc3b19c574389985446148402998287dae",
"sha256:48719e356bb8b42991bdbb1e8b83223757b93789c00910a616a071910ca4a64d"
],
"markers": "python_version >= '3.5'",
"version": "==0.4.2"
},
"uwsgi": {
"hashes": [
"sha256:88ab9867d8973d8ae84719cf233b7dafc54326fcaec89683c3f9f77c002cdff9"
],
"index": "pypi",
"version": "==2.0.20"
}
},
"develop": {}
}
from enum import Enum
class Operations(Enum):
"""
This enum is the distribute all possible operations globally in this app.
"""
REGISTER = "register"
LOGIN = "login"
LOGOUT = "logout"
from django.contrib import admin
from .models import *
class RelationshipInline(admin.StackedInline):
"""
This is the stackable inline representation of the relationships.
It will use the from_account as an foreign key.
It will not display any more then relationships then necessary (extra = 0)
"""
model = Relationship
fk_name = 'from_account'
extra = 0
class StatementsInline(admin.StackedInline):
"""
This is the stackable inline representation of the statements.
It will not display any more then statements then necessary (extra = 0)
"""
model = Statement
extra = 0
class AccountAdmin(admin.ModelAdmin):
"""
This is the admin for the accounts.
This will add the relationships of an account to admin interface.
This will add content of an account to admin interface.
"""
inlines = [RelationshipInline, StatementsInline]
class StatementHashtagInline(admin.StackedInline):
"""
This is the stackable representation of the tagging of an statement with an hashtag.
"""
model = HashtagTagging
fk_name = 'statement'
extra = 0
class StatementAccountInline(admin.StackedInline):
"""
This is the stackable representation of the mentioning of an account within an statement.
"""
model = AccountTagging
fk_name = 'statement'
extra = 0
class StatementReactionInline(admin.StackedInline):
"""
This is the stackable representation of the reaction relation between statements.
"""
model = Reaction
fk_name = 'parent'
extra = 0
class StatementAdmin(admin.ModelAdmin):
"""
This ist the admin for the statements.
It will add the tagging of statements with hashtags to the admin interface.
"""
inlines = [StatementHashtagInline,
StatementAccountInline, StatementReactionInline]
admin.site.register(Account, AccountAdmin)
admin.site.register(Relationship)
admin.site.register(Hashtag)
admin.site.register(HashtagTagging)
admin.site.register(AccountTagging)
admin.site.register(Reaction)
admin.site.register(Statement, StatementAdmin)
from django.apps import AppConfig
class AuthenticationConfig(AppConfig):
name = 'core'
# Generated by Django 3.1.2 on 2020-12-02 11:13
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
]
operations = [
migrations.CreateModel(
name='Account',
fields=[
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='auth.user')),
('image', models.ImageField(default='account/default/Argunaut.png', upload_to='account/images')),
('biography', models.CharField(default='Hey there, nice to meet you!', max_length=1000))
],
),
migrations.CreateModel(
name='Relationship',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True, db_index=True)),
('from_account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='from_account', to='core.account')),
('to_account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='to_account', to='core.account')),
],
options={
'ordering': ('-created',),
},
),
migrations.AddField(
model_name='account',
name='related_to',
field=models.ManyToManyField(blank=True, default=None, related_name='related_by', through='core.Relationship', to='core.Account'),
),
migrations.CreateModel(
name='Statement',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('content', models.CharField(max_length=120)),
('created', models.DateTimeField(auto_now_add=True, db_index=True)),
('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.account')),
],
options={
'ordering': ('-created',),
},
),
migrations.CreateModel(
name='Hashtag',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('tag', models.CharField(max_length=30)),
('created', models.DateTimeField(auto_now_add=True, db_index=True)),
],
),
migrations.CreateModel(
name='HashtagTagging',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True, db_index=True)),
('hashtag', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='hashtag', to='core.hashtag')),
('statement', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.statement')),
],
options={
'ordering': ('-created',),
},
),
migrations.AddField(
model_name='statement',
name='tagged',
field=models.ManyToManyField(blank=True, default=None, related_name='tags', through='core.HashtagTagging', to='core.Hashtag'),
),
migrations.CreateModel(
name='AccountTagging',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True, db_index=True)),
('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='account', to='core.account')),
('statement', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.statement')),
],
options={
'ordering': ('-created',),
},
),
migrations.AddField(
model_name='statement',
name='mentioned',
field=models.ManyToManyField(blank=True, default=None, related_name='mentions', through='core.AccountTagging', to='core.Account'),
),
migrations.CreateModel(
name='Reaction',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True, db_index=True)),
('vote', models.PositiveSmallIntegerField(choices=[(1, 'like'), (2, 'dislike')], default=1)),
('child', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='child', to='core.statement')),
('parent', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='parent', to='core.statement')),
],
options={
'ordering': ('-created',),
},
),
migrations.AddField(
model_name='statement',
name='reactions',
field=models.ManyToManyField(blank=True, default=None, related_name='reaction_of', through='core.Reaction', to='core.Statement'),
),
]
# Generated by Django 3.2.9 on 2021-11-11 12:20
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='accounttagging',
name='id',
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
),
migrations.AlterField(
model_name='hashtag',
name='id',
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
),
migrations.AlterField(
model_name='hashtagtagging',
name='id',
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
),
migrations.AlterField(
model_name='reaction',
name='id',
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
),
migrations.AlterField(
model_name='relationship',
name='id',
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
),
migrations.AlterField(
model_name='statement',
name='id',
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
),
]
import logging
from typing import List, Optional, Tuple
import re
from django.contrib.auth.models import User
from django.core.files.uploadedfile import InMemoryUploadedFile
from django.db import models
from django.apps import apps
logger = logging.getLogger(__name__)
class Statement(models.Model):
"""
This model represents an statement of an specific account.
"""
author = models.ForeignKey('core.Account', on_delete=models.CASCADE)
content = models.CharField(max_length=120, blank=False)
created = models.DateTimeField(auto_now_add=True, db_index=True)
# Add an hashtag between statements and hashtags over the tagging model
tagged = models.ManyToManyField('Hashtag',
blank=True,
through='HashtagTagging',
symmetrical=False,
related_name='tags',
default=None)
mentioned = models.ManyToManyField('core.Account',
blank=True,
through='AccountTagging',
symmetrical=False,
related_name='mentions',
default=None)
reactions = models.ManyToManyField('self',
blank=True,
through='Reaction',
symmetrical=False,
related_name='reaction_of',
default=None)
def __str__(self):
return "{author} says: {content}".format(author=self.author.user.username, content=self.content)
class Meta:
ordering = ('-created',)
def save(self, *args, **kwargs) -> None:
"""
This method adds hashtags relations after the statement is saved.
Save is ran after update or create.
:param args: Not used.
:param kwargs: Not used.
:return: None
"""
super(Statement, self).save(*args, **kwargs)
# resolve hashtags after saving the statement
used_hashtags: List[str] = self.__extract_hashtags()
for used_hashtag in used_hashtags:
result: Tuple[Hashtag, bool] = Hashtag.objects.get_or_create(tag=used_hashtag)
hashtag: Hashtag = result[0]
self.add_hashtag(hashtag=hashtag)
used_mentions: List[str] = self.__extract_mentioning()
# resolve mentions after saving the statement
for used_mention in used_mentions:
account: 'core.Account' = apps.get_model("core", "Account").objects.filter(
user__username=used_mention).first()
if account:
self.add_mentioning(account=account)
def add_reaction(self, reaction_statement: 'Statement', vote: int) -> Tuple['Reaction', bool]:
"""
This method adds an reaction to the calling statement.
:param reaction_statement: The statement to be added as an reaction.
:param vote: The vote of the reaction regarding the parent element. (Like 1, Dislike 0)
:return: The reaction as well as the status if the reaction was already existing.
"""
reaction, created = Reaction.objects.get_or_create(
parent=self,
child=reaction_statement,
vote=vote
)
return reaction, created
def remove_as_reaction(self) -> bool:
"""
This method removes the calling statement as an reaction for the parent.
:return: Status if the reaction was deleted or not.
"""
deleted, _ = Reaction.objects.filter(child=self).delete()
return deleted
def get_reactions(self) -> List['Reaction']:
"""
This method returns all reaction of the calling statement.
:return: List of all reactions to the calling statement
"""
return list(Reaction.objects.filter(parent=self))
def get_reaction_to_parent(self) -> Optional[List['Reaction']]:
"""
This method returns the reaction relation to the parent if this statement is used as an reaction.
:return: Reaction to the parent if there is one. Else none.
"""
reaction: List[Reaction] = Reaction.objects.filter(child=self)
if len(reaction) == 0:
return None
# todo: is there a way to use the instance and not a list in the reaction serialize, if so, also change frontend
return reaction
def get_parent(self) -> Optional['Statement']:
"""
This method is for getting the parent if of an statement if this statement is an reaction.
:return: The parent statement of the reaction. If there is no parent then None is returned.
"""
parents: List['Statement'] = self.reaction_of.all()
if len(parents) == 0:
return None
return parents[0]
def __extract_hashtags(self) -> List[str]:
"""
This method extracts the hashtag of the content.
Hashtags are alpha numeric words.
:return: List of all hashtags used in the content of the statement.
"""
return re.findall(r"#(\w+)", self.content)
def add_hashtag(self, hashtag: 'Hashtag'):
"""
This method is for adding an hashtag to the corresponding statement.
:param hashtag: The hashtag to be added.
:return: True if the hashtag was created, false otherwise.
"""
tagging, created = HashtagTagging.objects.get_or_create(statement=self, hashtag=hashtag)
return created
def get_hashtags(self) -> List['Hashtag']:
"""
This method is to get all hashtags of the calling statement.
:return: List of all hashtags of the calling statement.
"""
return list(self.tagged.all())
def remove_hashtag(self, hashtag: 'Hashtag'):
"""
This method is used to delete an specific hashtag for the calling statement.
:param hashtag: The hashtag to be deleted.
:return: True if the hashtag was deleted, false else.
"""
deleted: bool = HashtagTagging.objects.filter(
statement=self,
hashtag=hashtag
).delete()
return deleted
def __extract_mentioning(self) -> List['core.Account']:
"""
This method extracts the mentions of accounts in the calling statement.
Accounts names are alpha numeric words.
:return: List of all accounts mentioned in the calling statement.
"""
return re.findall(r"@(\w+)", self.content)
def add_mentioning(self, account: 'core.Account'):
"""
This method is for adding an mention of an account to the corresponding statement.
:param account: The account to be mentioned.
:return: True if the mention was created, false otherwise.
"""
mentioning, created = AccountTagging.objects.get_or_create(statement=self, account=account)
return created
def get_mentioning(self) -> List['core.Account']:
"""
This method is to get all accounts mentioned by the calling statement.
:return: List of all accounts mentioned by the calling statement.
"""
return list(self.mentioned.all())
def remove_mentioning(self, account: 'core.Account'):
"""
This method is used to delete an specific mentioning of an account for the calling statement.
:param account: The account to be unmentioned.
:return: True if the hashtag was deleted, false else.
"""
deleted: bool = AccountTagging.objects.filter(
statement=self,
account=account
).delete()
return deleted
class Hashtag(models.Model):
"""
This model represents an hashtag which can be added to specific contents.
"""
tag = models.CharField(max_length=30, blank=False)
created = models.DateTimeField(auto_now_add=True, db_index=True)
def __str__(self):
return "#{tag}".format(tag=self.tag)
class Tagging(models.Model):
"""
This model represents the relation between any type of content and an hashtag
"""
# What is the corresponding statement?
statement = models.ForeignKey(Statement, on_delete=models.CASCADE)
# When was this tagging created?
created = models.DateTimeField(auto_now_add=True, db_index=True)
# The default manager
objects = models.Manager()
class Meta:
abstract = True
class HashtagTagging(Tagging):
"""
This model is to represent the tagging of an statement with an hashtag.
"""
# Which hashtag should be tagged?
hashtag = models.ForeignKey(Hashtag, related_name='hashtag', on_delete=models.CASCADE)
class Meta:
ordering = ('-created',)
def __str__(self):
return "{statement} tagged with {hashtag}".format(statement=self.statement, hashtag=self.hashtag)
class AccountTagging(Tagging):
"""
This model is to represent the mention of an account within an statement.
"""
# Which account should be mentioned?
account = models.ForeignKey('core.Account', related_name='account', on_delete=models.CASCADE)
class Meta:
ordering = ('-created',)
def __str__(self):
return "{statement} mentioned {account}".format(statement=self.statement, account=self.account)
class Reaction(models.Model):
# Who is the parent element of the reaction
parent = models.ForeignKey(Statement, related_name='parent', on_delete=models.CASCADE)
# Who is the reaction to the parent
child = models.ForeignKey(Statement, related_name='child', on_delete=models.CASCADE)
# When was this reaction created?
created = models.DateTimeField(auto_now_add=True, db_index=True)
# The relation must have an clear vote
vote = models.PositiveSmallIntegerField(
choices=[
(1, "like"),
(2, "dislike")
],
default=1
)
# The default manager
objects = models.Manager()
class Meta:
ordering = ('-created',)
def __str__(self):
return "{child_author} {reaction}s >>{parent_content}<< of {parent_author} because >>{child_content}<<".format(
child_author=self.child.author.user.username,
child_content=self.child.content,
reaction=self.get_vote_display(),
parent_content=self.parent.content,
parent_author=self.parent.author.user.username,
)
class Account(models.Model):
"""
This model is for handling user accounts.
Therefore all data regarding an user is stored in this model.
The account is separated from the user since the default user model is used.
"""
# Link the account to an user
user: User = models.OneToOneField(to=User,
on_delete=models.CASCADE,
primary_key=True)
# Add an relationship between accounts over the relationship model
related_to = models.ManyToManyField('self',
blank=True,
through='Relationship',
symmetrical=False,
related_name='related_by',
default=None)
# This is the image of the account
image = models.ImageField(upload_to='account/images',
default='account/default/Argunaut.png')
biography = models.CharField(blank=False,
max_length=1000,
default="Hey there, nice to meet you!".format(user))
# The default manager
objects = models.Manager()
def __str__(self):
return "{username}".format(username=self.user.username)
def add_relationship(self, account: 'Account') -> bool:
"""
This method adds an relationship for an instance.
:param account: Who should be added to an relation with the instance.
:return: True if the relationship was created, false otherwise.
"""
relationship, created = Relationship.objects.get_or_create(
from_account=self,
to_account=account)
return created
def remove_relationship(self, account: 'Account'):
"""
This method deletes the relationship to an other Account.
:param account: The user with whom the relationship is to be terminated.
:return: True if the relationship was deleted, false otherwise.
"""
deleted: bool = Relationship.objects.filter(
from_account=self,
to_account=account).delete()
return deleted
def get_related_to(self) -> List['Account']:
"""
This method returns all accounts related to the calling instance of this method.
Therefore this returns accounts the calling accounts relates to.
:return: All related accounts of the calling instance.
"""
return list(self.related_to.filter(to_account__from_account=self))
def get_related_by(self) -> List['Account']:
"""
This method returns all accounts who relates with the calling account.
:return: All accounts who relates to the calling account.
"""
return list(self.related_by.filter(from_account__to_account=self))
def get_statements(self) -> List['Statement']:
"""
This method returns all all statements made by the calling account.
:return: All statements made by the calling account.
"""
return list(self.statement_set.all())
def add_statement(self, content: str) -> Statement:
"""
This methods add a statement for the calling account.
:param content: The content of the statement.
:return: The added statement.
"""
statement: Statement = Statement(author=self, content=content)
self.statement_set.add(statement, bulk=False)
return statement
def update_image(self, new_image: InMemoryUploadedFile):
"""
This method overwrites the image of an account.
If the account uses the default image the image will not be deleted.
:param new_image: The new image to be added for the account.
:return: Nothing
"""
if self.image != "account/default/Argunaut.png":
self.image.delete(save=True)
self.image = new_image
self.save()
def update_biography(self, new_biography: str):
"""
This method overwrites the biography of an account if it is not none.
:param new_biography: The new biography to be added for the account.
:return: Nothing
"""
if new_biography and self.biography != new_biography:
self.biography = new_biography
self.save()
class Relationship(models.Model):
"""
This model handles relations between users.
By using this model it is possible to create more detailed relationships.
"""
# Who wants to have an relation?
from_account = models.ForeignKey(Account, related_name='from_account', on_delete=models.CASCADE)
# To whom should a relationship be established?
to_account = models.ForeignKey(Account, related_name='to_account', on_delete=models.CASCADE)
# When was this relation created?
created = models.DateTimeField(auto_now_add=True, db_index=True)
# The default manager
objects = models.Manager()
class Meta:
ordering = ('-created',)
def __str__(self):
return "{from_user} related to {to_user}".format(from_user=self.from_account, to_user=self.to_account)
\ No newline at end of file
from rest_framework import serializers
from ..models import Account
from ..serializers.authenticationSerializers import UserPublicSerializer, UserOwnSerializer
from ..serializers.contentSerializers import StatementSerializer
class AccountSerializer(serializers.ModelSerializer):
"""
This serializer serializes the accounts and their data.
It can be used to serialize accounts.
"""
user = UserPublicSerializer()
class Meta:
model = Account
fields = ('user', 'image',)
class AccountPublicSerializer(serializers.ModelSerializer):
"""
This serializer serializes the public data of accounts.
It can be used to get all public data of the accounts.
"""
# this is the parent account
user = UserPublicSerializer()
# these are the related (child) accounts
related_to = serializers.ListField(source='get_related_to', child=AccountSerializer())
# these are all statements of the account
statements = serializers.ListField(source='get_statements', child=StatementSerializer())
# check if the calling account knows the account as friend.
is_friend = serializers.SerializerMethodField('_is_friend')
# this field is to check if the one calls his own public data.
self_request = serializers.SerializerMethodField('_self_request')
def _self_request(self, obj: Account):
return self.context["calling_account"].user.id == obj.user.id
def _is_friend(self, obj: Account):
"""
This intern method checks for the calling account if the requested account is a friend or not!
:param obj: The requested account.
:return: True if obj is a friend of the calling account.
"""
account: Account = self.context["calling_account"]
return obj in account.get_related_to()
class Meta:
model = Account
fields = ('user', 'image', 'biography', 'related_to', 'statements', 'is_friend', 'self_request',)
class AccountTinySerializer(AccountPublicSerializer):
"""
This serializer serializes the public data of accounts but it is shorter.
It can be used to get all shortened public data of the accounts.
"""
# this is the parent account
user = UserPublicSerializer()
class Meta:
model = Account
fields = ('user', 'image', 'related_to')
class AccountOwnSerializer(AccountPublicSerializer):
"""
This serializer is for the representation of an own account.
It shows more information to the user then the public serializer.
Todo: Make statements use the StatementSerializer.
"""
# this is the parent account, it overwrites the field of AccountPublicSerializer
user = UserOwnSerializer()
# to see how follows the own account
related_by = serializers.ListField(source='get_related_by', child=AccountSerializer())
class Meta:
model = Account
fields = ('user', 'image', 'biography', 'related_by', 'related_to', 'statements')
import logging
from typing import OrderedDict
from django.contrib.auth import authenticate
from django.contrib.auth.models import User
from django.core.validators import RegexValidator
from rest_framework import serializers
from rest_framework.validators import UniqueValidator
logger = logging.getLogger(__name__)
class UserDefaultSerializer(serializers.ModelSerializer):
"""
This is the default serializer to get users and validate their data.
"""
username = serializers.CharField()
def validate(self, data: OrderedDict):
"""
This method validates the provided data to check if there is an user existing.
:param data: Data describing the user.
:return: The validated data.
"""
username: str = data.get("username", None)
password: str = data.get("password", None)
if not username:
raise serializers.ValidationError("This username is missing", code='blank')
if not password:
raise serializers.ValidationError("This password is missing", code='blank')
user: User = authenticate(username=username, password=password)
if not user:
raise serializers.ValidationError({"user": "There is no user like this"}, code='invalid')
return data
class Meta:
model = User
fields = ['username', 'password']
class UserRegisterSerializer(serializers.ModelSerializer):
"""
This serializer is used for user registration and their validation.
A user should have a unique valid and non-empty username, email and password.
"""
username = serializers.CharField(
required=True,
validators=[
UniqueValidator(queryset=User.objects.all()),
RegexValidator(r'^[0-9a-zA-Z_]*$', 'Only aA-zZ, 0-9, _ are allowed.')
]
)
email = serializers.EmailField(
required=True,
validators=[
UniqueValidator(queryset=User.objects.all())
]
)
def create(self, validated_data):
"""
This method creates a new user by the validated data.
:param validated_data: The validated data providing all information to create an user.
:return: The created user.
"""
user: User = User.objects.create_user(**validated_data)
return user
class Meta:
model = User
fields = ['username', 'email', 'password']
class UserPublicSerializer(serializers.ModelSerializer):
"""
This serializer is for the public representation of the user.
It only shows the username.
"""
class Meta:
model = User
fields = ('id', 'username',)
class UserOwnSerializer(serializers.ModelSerializer):
"""
This serializer is for the own representation of the user.
"""
class Meta:
model = User
fields = ('id', 'username', 'email', 'date_joined',)
from typing import OrderedDict
from django.apps import apps
from django.db.models import QuerySet
from rest_framework import serializers
from ..models import Account, Statement, Hashtag, Reaction, HashtagTagging
from ..serializers.authenticationSerializers import UserPublicSerializer
class HashtagSerializer(serializers.ModelSerializer):
"""
This serializer can be used to serialize hashtags.
"""
class Meta:
model = Hashtag
fields = ("id", "tag",)
class TrendingHashtagSerializer(HashtagSerializer):
"""
This serializer is for the serialization of trending hashtags.
In addition to the serialization of the hashtags this serializer adds the usage and participants.
This serializer needs an context. The context must include:
- counted: Dict with the hashtag id as key and the usage as value.
- calling_user: Id of the calling user.
"""
count = serializers.SerializerMethodField('_count')
participants = serializers.SerializerMethodField('_participants')
def _count(self, obj: Hashtag) -> int:
"""
This method adds the precalculated usage of the hashtag.
:param obj: The current hashtag.
:return: The amount of usage of the specific hashtag.
"""
return self.context["counted"][obj.id]
def _participants(self, obj: Hashtag) -> OrderedDict:
"""
This method takes all usage of the hashtag in combination ith an statements and returns the authors.
Therefore one can get the participants of an conversation regarding this hashtag.
The calling account is excluded from the results.
:param obj: The current hashtag.
:return: The participants of an hashtag with out the requesting account.
"""
tagged: QuerySet[HashtagTagging] = HashtagTagging.objects.filter(hashtag=obj.id)
tagged = tagged.exclude(statement__author=self.context["calling_user"])
authors: QuerySet[Account] = Account.objects.filter(
user__id__in=tagged.values_list('statement__author', flat=True).distinct()
)
serializer: AccountSerializer = AccountSerializer(instance=authors, many=True)
return serializer.data
class Meta:
model = Hashtag
fields = HashtagSerializer.Meta.fields + ('count', 'participants')
class AccountSerializer(serializers.ModelSerializer):
"""
This serializer serializes the accounts and their data.
It can be used to serialize accounts.
Todo: Replace the account mentioning with users to remove this dependencies.
"""
user = UserPublicSerializer()
class Meta:
model = apps.get_model("core", "Account")
fields = ('user', 'image',)
class SimpleStatementSerializer(serializers.ModelSerializer):
"""
This serializer serializes the statements and their content.
It can be used to serialize content of a statement.
"""
author = AccountSerializer()
tagged = serializers.ListField(source='get_hashtags', child=HashtagSerializer())
mentioned = serializers.ListField(source='get_mentioning', child=AccountSerializer())
class Meta:
model = Statement
fields = ('id', 'author', 'content', 'tagged', 'mentioned', 'created')
class ReactionSerializer(serializers.ModelSerializer):
"""
This serializer is for reactions.
It will return the serialized reaction and shows the id, vote and the child statement.
"""
child = SimpleStatementSerializer()
parent = SimpleStatementSerializer()
class Meta:
model = Reaction
fields = ('id', 'vote', 'child', 'parent')
class StatementSerializer(SimpleStatementSerializer):
"""
This is more then the simple statement serializer.
With this serializer one can also get information regarding the connection to the parent.
Todo: Is there a way to combine each statement with parent and child information and shorten the frontend?
"""
relation_to_parent = serializers.ListField(source='get_reaction_to_parent', child=ReactionSerializer())
class Meta:
model = Statement
fields = SimpleStatementSerializer.Meta.fields + ('relation_to_parent',)
class StatementObservationSerializer(StatementSerializer):
"""
This serializer is for the statement observation.
Therefore the reactions are extended in the fields.
"""
reactions = serializers.ListField(source='get_reactions', child=ReactionSerializer())
class Meta:
model = Statement
fields = StatementSerializer.Meta.fields + ('reactions',)
This diff is collapsed.
from django.urls import path
from ..views.accountViews import *
urlpatterns = [
path('show/<int:id>/', PublicAccounts.as_view(), name='show'),
path('show/all/', AllPublicAccounts.as_view(), name='showAll'),
path('show/own/', OwnAccount.as_view(), name='own'),
path('update/', OwnAccountUpdate.as_view(), name='update'),
path('follow/<int:id>/', OwnAccountFollow.as_view(), name='follow'),
path('unfollow/<int:id>/', OwnAccountUnfollow.as_view(), name='unfollow'),
path('operation/add/statement/', AddStatement.as_view(), name='addStatement'),
]
\ No newline at end of file
from django.conf.urls import url
from rest_framework.authtoken.views import obtain_auth_token
from ..views.authenticationViews import *
urlpatterns = [
url(r'obtain/', obtain_auth_token, name='obtain'),
url(r'register/', Register.as_view(), name='register'),
url(r'login/', Login.as_view(), name='login'),
url(r'logout/', Logout.as_view(), name='logout'),
url(r'validate/', TokenValidation.as_view(), name='validate'),
]
from django.urls import path
from ..views.contentViews import *
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('trending/hashtag/', ShowTrendingHashtag.as_view(), name="show_trending_hashtags"),
]
\ No newline at end of file
from django.urls import path
from ..views.searchViews import *
urlpatterns = [
path('', Search.as_view(), name='search'),
]
\ No newline at end of file
import logging
from rest_framework import status
from rest_framework.request import Request
from rest_framework.response import Response
from . import Operations
from .serializers.authenticationSerializers import UserRegisterSerializer, UserDefaultSerializer
logger = logging.getLogger(__name__)
def validate_request_data_for(operation: Operations, request: Request) -> Response:
"""
This method validates the a request regarding user its user information.
:param operation: Which operation was used? This influences which serializer is used and which fields are checked.
:param request: The request regarding an user.
:return: 400_BAD_REQUEST if the provided data is sparse or one of the values is empty or there are duplicated values.
201_CREATE if the new user is created.
200_OK if the user is valid.
"""
serializer = UserDefaultSerializer(data=request.data)
if operation is Operations.REGISTER:
serializer = UserRegisterSerializer(data=request.data)
if serializer.is_valid():
if operation is Operations.REGISTER:
serializer.save() # this calls update or create based on the existence of the corresponding instance
return Response(data=serializer.validated_data, status=status.HTTP_201_CREATED)
return Response(data=serializer.validated_data, status=status.HTTP_200_OK)
else:
return Response(data=serializer.errors, status=status.HTTP_400_BAD_REQUEST)
# Create your views here.
import logging
from io import BytesIO
from PIL import Image
from django.contrib.auth.models import User
from django.core.files.uploadedfile import InMemoryUploadedFile
from django.db.models import Q
from rest_framework import status
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.views import APIView
from ..models import Account, Statement
from ..serializers.accountSerializers import AccountPublicSerializer, AccountOwnSerializer
from ..serializers.contentSerializers import ReactionSerializer, StatementSerializer
logger = logging.getLogger(__name__)
class PublicAccounts(APIView):
"""
This view is used to represent the accounts.
It can be used to get public information about accounts from the perspective of the calling account.
To get this information the calling account must use its token.
"""
authentication_classes = [TokenAuthentication]
permission_classes = (IsAuthenticated,)
def get(self, request: Request, *args, **kwargs):
"""
This method is used to get all public information regarding a specific account.
:param request: Not used.
:param args: Not used.
:param kwargs: Should have the id of the requested account (see view.py)
:return:
"""
calling_account: Account = Account.objects.filter(user=request.user).first()
account: Account = Account.objects.filter(user=int(kwargs.get("id")))
serializer: AccountPublicSerializer = AccountPublicSerializer(instance=account,
many=True,
context={"calling_account": calling_account})
return Response(data=serializer.data, status=status.HTTP_200_OK)
class AllPublicAccounts(APIView):
"""
This view is for showing all public accounts.
It requires the user token to see which account is calling the overview.
The calling account is then removed from the result.
"""
authentication_classes = [TokenAuthentication]
permission_classes = (IsAuthenticated,)
def get(self, request: Request):
"""
This method gets all available public accounts.
Furthermore the calling account is excluded from the result.
:param request: Used to get the calling account.
:return:
"""
calling_account: Account = Account.objects.filter(user=request.user).first()
accounts: Account = Account.objects.filter(~Q(user=request.user))
serializer: AccountPublicSerializer = AccountPublicSerializer(instance=accounts,
many=True,
context={"calling_account": calling_account})
return Response(data=serializer.data, status=status.HTTP_200_OK)
class OwnAccount(APIView):
"""
This view is used to represent the private account of an user.
To access the information one have to use its token.
"""
authentication_classes = [TokenAuthentication]
permission_classes = (IsAuthenticated,)
def get(self, request: Request):
"""
This method is used to get the own account data.
:param request: To access the user from its token.
:return: Data regarding the own account.
"""
account: Account = Account.objects.filter(user=request.user)
serializer: AccountOwnSerializer = AccountOwnSerializer(instance=account, many=True)
return Response(data=serializer.data, status=status.HTTP_200_OK)
class OwnAccountFollow(APIView):
"""
This view is for adding a follow relation from the calling account to the targeted one.
To access this view the requesting account has to use its token.
"""
authentication_classes = [TokenAuthentication]
permission_classes = (IsAuthenticated,)
def post(self, request: Request, *args, **kwargs):
own_account: Account = Account.objects.filter(user=request.user).first()
foreign_account: Account = Account.objects.filter(user=kwargs.get("id")).first()
if not foreign_account:
return Response(status=status.HTTP_409_CONFLICT)
created: bool = own_account.add_relationship(foreign_account)
if created:
return Response(status=status.HTTP_200_OK)
return Response(status=status.HTTP_409_CONFLICT)
class OwnAccountUnfollow(APIView):
"""
This view is for deleting a follow relation from the calling account to the targeted one.
To access this view the requesting account has to use its token.
"""
authentication_classes = [TokenAuthentication]
permission_classes = (IsAuthenticated,)
def post(self, request: Request, *args, **kwargs):
own_account: Account = Account.objects.filter(user=request.user).first()
foreign_account: Account = Account.objects.filter(user=kwargs.get("id")).first()
if not foreign_account:
return Response(status=status.HTTP_409_CONFLICT)
deleted: bool = own_account.remove_relationship(foreign_account)
if deleted:
return Response(status=status.HTTP_200_OK)
return Response(status=status.HTTP_409_CONFLICT)
class OwnAccountUpdate(APIView):
"""
This view is for updating the account data.
It will update an image.
"""
authentication_classes = [TokenAuthentication]
permission_classes = (IsAuthenticated,)
@staticmethod
def put(request: Request):
"""
This method updates the image as well as the biography.
:param request: The request send by the user.
:return: A response with an 200 status.
"""
user: User = request.user
account: Account = Account.objects.filter(user=user).first()
if "biography" in request.data.keys():
biography: str = request.data["biography"]
account.update_biography(biography)
if "file" in request.FILES.keys():
file: InMemoryUploadedFile = request.FILES["file"]
file.name = "{name}.{extension}".format(name="account{user}{secret}".format(user=user, secret=hash(user)),
extension="jpeg")
# compress image
image: Image = Image.open(file)
image = image.convert('RGB')
io_stream = BytesIO()
image.save(io_stream, format="JPEG", quality=50, optimize=True)
compressed_image: InMemoryUploadedFile = InMemoryUploadedFile(file=io_stream,
field_name=None,
name=file.name,
content_type="image/jpeg",
size=io_stream.tell(),
charset=None)
# update with compressed image
account.update_image(compressed_image)
return Response(status=status.HTTP_200_OK)
class AddStatement(APIView):
"""
This view serves to add statements for an specific user.
The user must be authenticated.
"""
authentication_classes = [TokenAuthentication]
permission_classes = (IsAuthenticated,)
@staticmethod
def post(request: Request):
"""
This method handles the post of an new statement.
:param request: The request to be handled, containing the input.
:return: Response with an 200 OK if everything is okay. 400 if there is no statement input.
If there is an reaction then this will return 200 and the serialized reaction.
This is necessary, because the frontend must only add this element to the overview of reaction and needs the
analysis regarding the mentions and tags by the backend.
todo: Make it possible for single statement
"""
account: Account = Account.objects.filter(user=request.user).first()
statement: str = request.data.get("input", None)
if not statement:
return Response(status=status.HTTP_400_BAD_REQUEST)
statement: Statement = account.add_statement(statement)
# if there is an reaction given then is must be added to the parent element.
reaction: dict = request.data.get("reaction", None)
if reaction:
parent: Statement = Statement.objects.get(id=reaction.get("to"))
vote: int = 1 if reaction.get("relation") == "support" else 2
reaction, _ = parent.add_reaction(reaction_statement=statement, vote=vote)
serializer: ReactionSerializer = ReactionSerializer(instance=reaction, many=False)
return Response(status=status.HTTP_200_OK, data=serializer.data)
serializer: StatementSerializer = StatementSerializer(instance=statement, many=False)
return Response(status=status.HTTP_200_OK, data=serializer.data)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment