Commit c86e24da authored by Marc Feger's avatar Marc Feger
Browse files

Add first implementation for the-social-network core

parent e390fd69
.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.