Compare commits

..

77 Commits

Author SHA1 Message Date
Jukoga 8faf73adc1 Merge pull request 'Update dependencies and restart policy' (#8) from requirements-update into master
Reviewed-on: #8
2026-03-04 20:31:51 +01:00
Jukoga dcde29a8bb update docker-compose and requirements for database service configuration 2026-03-04 20:31:07 +01:00
Jukoga c2ecdc9bf6 networkmode is needed for the db 2025-12-16 17:52:28 +01:00
Jukoga f9a5ff3810 add restart policy to docker-compose.yml 2025-12-16 17:47:56 +01:00
Jukoga 66e59891f4 update dependencies in requirements.txt and remove network_mode from docker-compose.yml 2025-12-16 17:34:17 +01:00
Jukoga bf42e13626 better linebreak for test 2025-12-16 17:08:11 +01:00
Jukoga fd5d944cdf new text 2025-10-29 19:53:14 +01:00
Jukoga 8edffa16d5 more precise Dockerfile 2025-10-29 19:53:04 +01:00
Jukoga d7732c5f14 more margin 2025-05-18 21:17:57 +02:00
Jukoga 2f0c2f4d7e Black Background 2025-05-18 21:09:00 +02:00
Jukoga 73e43f8f4b links are colored 2025-05-18 20:35:26 +02:00
Jukoga 936b3ec2f6 bigger text and button 2025-05-18 20:35:19 +02:00
Jukoga ca72a91d20 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	templates/index.html
2024-12-01 21:00:48 +01:00
Jukoga 93e1ac1f1b changed the battle link 2024-12-01 20:59:52 +01:00
Surya a6abab998e Updated the Brand Logo and Name in the navbar 2024-09-30 09:35:31 +02:00
Surya 12921e0e33 Updated the 'TO BATTLE' button
Now the 'TO BATTLE' button links back to the link of the Official Beyond Heroes Discord Server
2024-09-30 08:57:25 +02:00
Surya e84eff41a1 Uploading the new white logo 2024-09-29 11:48:10 +02:00
Surya 57bfe200fb Upload files to "media" 2024-09-27 09:01:09 +02:00
Surya d06e3899cf Updated the Font Family and Logo 2024-09-27 09:00:52 +02:00
Surya cd40dd57f9 Added the new Logo Image 2024-09-26 10:28:08 +02:00
Surya 5273b06cf2 Update templates/support.html
changed the page content and added the required links
2024-08-14 09:31:00 +02:00
Jukoga 32c5a88f1c the migrations drive me crazy 2024-08-12 23:08:46 +02:00
Jukoga 2cf5562d04 deleted migrations 2024-08-12 23:07:30 +02:00
Surya 74baf03ae7 Delete server.bat
There is no need of this file as it no longer provides the required functionality.
2024-08-05 13:22:44 +02:00
Jukoga c0ac934c64 updated the README.md again 2024-08-05 13:19:03 +02:00
Surya e7a1572905 Merge pull request 'made everything to a docker file' (#6) from dockerisation into master
Reviewed-on: #6
This makes the Site Ready for Deployment.
2024-08-05 13:17:28 +02:00
Jukoga c916a2f583 updated the README.md 2024-08-05 13:13:59 +02:00
root 2e1fd94c8f production ready 2024-08-05 13:01:21 +02:00
Jukoga 2c286dacf4 made everything to a docker file 2024-08-01 13:33:38 +02:00
Jukoga 812099ee7f removed pycache 2024-07-24 23:53:44 +02:00
Jukoga 80af0bb917 deleted the sql file 2024-07-24 23:39:33 +02:00
Jukoga 588b151acd added pycache to all paths 2024-07-24 23:35:39 +02:00
Jukoga 28c5a0982d Merge remote-tracking branch 'origin/master'
# Conflicts:
#	blog/views.py
#	db.sqlite3
#	requirements.txt
#	templates/base.html
2024-07-24 23:33:58 +02:00
surya 29692b81ac Cleanup
Cleaned up the Project and re-migrated all the models
2024-07-24 23:28:40 +02:00
Surya 8e87a99ff2 Update blog/views.py 2024-07-24 23:27:34 +02:00
Surya fca2a8e35f Update README.md 2024-07-24 23:24:52 +02:00
Surya e8ca2d76b0 Made the `SECRET` secret
Made the `SECRET KEY` secret by using .env
2024-07-24 23:24:48 +02:00
surya 233381c9c2 Cleanup
Cleaned up the Project and re-migrated all the models
2024-07-25 02:31:23 +05:30
Surya 072df100ca Delete templates/users/login.html 2024-07-24 22:51:38 +02:00
Surya 4d8011eec6 Delete templates/users/logout.html 2024-07-24 22:51:33 +02:00
Surya b3be15b09e Delete templates/users/profile.html 2024-07-24 22:51:28 +02:00
Surya dae1b0cb7f Delete templates/users/register.html 2024-07-24 22:51:23 +02:00
Surya 5d402e7d67 Delete users/models.py 2024-07-24 22:50:20 +02:00
Surya e4c441848e Delete users/forms.py 2024-07-24 22:50:15 +02:00
Surya 212c7b033f Delete users/__init__.py 2024-07-24 22:50:09 +02:00
Surya 70ca6c2f48 Delete users/apps.py 2024-07-24 22:50:03 +02:00
Surya 19cb537864 Delete users/admin.py 2024-07-24 22:49:57 +02:00
Surya c1b711fe6a Delete users/signals.py 2024-07-24 22:49:47 +02:00
Surya 22a53d09ec Delete users/tests.py 2024-07-24 22:49:40 +02:00
Surya ee1a702618 Delete users/views.py 2024-07-24 22:49:32 +02:00
Surya cb4e2d2236 Delete users/urls.py 2024-07-24 22:49:18 +02:00
Surya 7bc99713c8 Update BH/urls.py 2024-07-24 22:48:46 +02:00
Surya 0e27feddb4 Update BH/settings.py 2024-07-24 22:43:16 +02:00
Surya 9de438c885 Delete templates/blog/blogCreate.html 2024-07-24 22:42:02 +02:00
Surya 9a6a88e695 Update blog/views.py 2024-07-24 22:41:34 +02:00
Surya 826d8aa56d Update blog/urls.py 2024-07-24 22:40:56 +02:00
Surya b15d095dc1 Update README.md 2024-07-24 22:31:09 +02:00
surya 1e5aecbd0b Merge branch 'master' of https://giteabh.srv-bw.beyond-heroes.com/Surya/Website 2024-07-25 01:59:31 +05:30
surya 72696e3302 Update base.html 2024-07-25 01:59:25 +05:30
Surya 3eed3e280d Made the `SECRET` secret
Made the `SECRET KEY` secret by using .env
2024-07-24 22:27:33 +02:00
Jukoga 79acc8163a deleted the blog create url 2024-07-24 21:50:06 +02:00
Jukoga d54da6f04d created .env file 2024-07-24 21:33:06 +02:00
Jukoga 02774e6094 delted everything that has todo with user 2024-07-24 21:25:47 +02:00
Jukoga 4ae7240d68 Merge branch 'refs/heads/MariaDB' into prod 2024-07-24 21:12:02 +02:00
Surya 21624d015b Merge pull request 'blog/create is now just accessible if you have the staff flag' (#3) from create_as_staff into master
Reviewed-on: #3
2024-07-20 04:35:43 +02:00
Jukoga 4edf0577e6 Changed from DBLite to MariaDB 2024-07-19 22:55:11 +02:00
Jukoga bc428552a5 blog/create is now just accessible if you have the staff flag 2024-07-17 12:39:00 +02:00
surya 9873a7db28 Changed Login Redirect
Changed Login Redirect from 'Home' to 'News'
2024-07-17 05:48:28 +05:30
surya bdc8451f22 Added the Post Button when Staff
Added the ability to post directly from the navbar when logged in as a staff member `User.is_staff == True`
2024-07-17 05:43:05 +05:30
Surya bef63db9d1 Update README.md
updated the development
2024-07-16 18:28:44 +02:00
surya 6a3eecddb9 Updated to Blog Detail
Improved the Blog Detailed page and added the link from news page to Blog Detailed page
2024-07-16 21:55:12 +05:30
surya e232fec118 Cleanup
cleaned up useless code
2024-07-16 14:57:51 +05:30
surya 9cae5bc994 Removed Posts
removed the `posts` class and allied functionality
2024-07-16 14:47:16 +05:30
Surya 62e1a1dd3a Merge pull request 'new_req' (#2) from new_req into master
Reviewed-on: #2
2024-07-15 16:49:40 +02:00
Jukoga f39c272265 updated README.md and fixed typos 2024-07-15 15:47:29 +02:00
Jukoga 2aba6b9853 cleaned requirements.txt 2024-07-15 15:47:11 +02:00
Surya fbe72c0185 Update README.md 2024-07-13 07:54:28 +02:00
95 changed files with 242 additions and 2026 deletions

5
.env.dev Normal file
View File

@ -0,0 +1,5 @@
SECRET_KEY=your_secret_key
DB_NAME=your_db_name
DB_USER=your_db_user
DB_PASSWORD=your_db_password
DB_ROOT_PASSWORD=your_db_root_password

17
.gitignore vendored
View File

@ -1,11 +1,15 @@
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files
__pycache__/ */__pycache__/
*.py[cod] *.py[cod]
*$py.class *$py.class
# C extensions # C extensions
*.so *.so
#added
staticfiles/
*/migrations/
blog/migrations/
# Distribution / packaging # Distribution / packaging
.Python .Python
build/ build/
@ -160,12 +164,3 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear # and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder. # option (not recommended) you can uncomment the following to ignore the entire idea folder.
.idea/ .idea/
# static
staticfiles/
# database
db.sqlite3
# todo
todo*

Binary file not shown.

Binary file not shown.

View File

@ -17,14 +17,19 @@ from dotenv import load_dotenv
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
TEMPLATES_DIR = os.path.join(BASE_DIR + '/templates') TEMPLATES_DIR = os.path.join(BASE_DIR + '/templates')
DEFAULT_AUTO_FIELD='django.db.models.AutoField'
# Quick-start development settings - unsuitable for production # Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ # See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/
# Load the Env
load_dotenv()
# SECURITY WARNING: keep the secret key used in production secret! # SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '!2g)+m+_h9fq9%il5+t5#qnj^9502or6$=2!$==v=i2*c#7q*m' SECRET_KEY = os.getenv('SECRET_KEY')
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True DEBUG = False
ALLOWED_HOSTS = ['localhost', '127.0.0.1', 'beyond-heroes.com', 'www.beyond-heroes.com'] ALLOWED_HOSTS = ['localhost', '127.0.0.1', 'beyond-heroes.com', 'www.beyond-heroes.com']
@ -34,43 +39,25 @@ ALLOWED_HOSTS = ['localhost', '127.0.0.1', 'beyond-heroes.com', 'www.beyond-hero
INSTALLED_APPS = [ INSTALLED_APPS = [
'crispy_forms', 'crispy_forms',
'crispy_bootstrap4', 'crispy_bootstrap4',
'blog.apps.BlogConfig', 'blog.apps.BlogConfig',
'users.apps.UsersConfig',
'api.apps.APIConfig',
'django.contrib.admin', 'django.contrib.admin',
'django.contrib.auth', 'django.contrib.auth',
'django.contrib.contenttypes', 'django.contrib.contenttypes',
'django.contrib.sessions', 'django.contrib.sessions',
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'django.contrib.sites', # for django-allauth
'corsheaders',
'dj_rest_auth',
'rest_framework',
'rest_framework.authtoken',
# 'allauth',
# 'allauth.account',
# 'allauth.socialaccount',
# 'dj_rest_auth.registration', # for api side user registration
] ]
MIDDLEWARE = [ MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware', 'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
'corsheaders.middleware.CorsMiddleware', # cors-headers
'django.middleware.common.CommonMiddleware', 'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware', 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware', 'django.contrib.messages.middleware.MessageMiddleware',
# 'allauth.account.middleware.AccountMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
] ]
CORS_ORIGIN_WHITELIST = ( 'http://localhost:3000', 'http://www.beyond-heroes.com')
ROOT_URLCONF = 'BH.urls' ROOT_URLCONF = 'BH.urls'
TEMPLATES = [ TEMPLATES = [
@ -97,8 +84,12 @@ WSGI_APPLICATION = 'BH.wsgi.application'
DATABASES = { DATABASES = {
'default': { 'default': {
'ENGINE': 'django.db.backends.sqlite3', 'ENGINE': 'django.db.backends.mysql',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 'NAME': os.getenv('DB_NAME'),
'USER': os.getenv('DB_USER'),
'PASSWORD': os.getenv('DB_PASSWORD'),
'HOST': 'db',
'PORT': '3306',
} }
} }
@ -121,17 +112,6 @@ AUTH_PASSWORD_VALIDATORS = [
}, },
] ]
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
# 'rest_framework.permissions.AllowAny',
'rest_framework.permissions.IsAuthenticated',
],
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.SessionAuthentication', # causes CSRF Token conflicts in API
'rest_framework.authentication.TokenAuthentication',
)
}
# Internationalization # Internationalization
# https://docs.djangoproject.com/en/3.0/topics/i18n/ # https://docs.djangoproject.com/en/3.0/topics/i18n/
@ -150,17 +130,11 @@ USE_TZ = True
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.0/howto/static-files/ # https://docs.djangoproject.com/en/3.0/howto/static-files/
# remember to `python manage.py collectstatic`
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
STATIC_URL = '/static/' STATIC_URL = '/static/'
MEDIA_ROOT = os.path.join(BASE_DIR + '/media') MEDIA_ROOT = os.path.join(BASE_DIR + '/media')
MEDIA_URL = '/media/' MEDIA_URL = '/media/'
CRISPY_TEMPLATE_PACK = 'bootstrap4' CRISPY_TEMPLATE_PACK = 'bootstrap4'
LOGIN_REDIRECT_URL = 'Home' LOGIN_REDIRECT_URL = 'News'
LOGIN_URL = 'Login' LOGIN_URL = 'Login'
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' # prints to console
SITE_ID = 1 # for django-allauth
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

View File

@ -1,4 +1,4 @@
"""BH URL Configuration """TechBlog URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see: The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/3.0/topics/http/urls/ https://docs.djangoproject.com/en/3.0/topics/http/urls/
@ -21,11 +21,6 @@ from django.urls import path, include
urlpatterns = [ urlpatterns = [
path('', include('blog.urls')), path('', include('blog.urls')),
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
path('api/v1/', include('api.urls')),
path('users/', include('users.urls')),
path('api-auth/', include('rest_framework.urls')),
path('api/v1/dj-rest-auth/', include('dj_rest_auth.urls')),
# path('api/v1/dj-rest-auth/register/', include('dj_rest_auth.registration.urls')),
] ]
if settings.DEBUG: if settings.DEBUG:

34
Dockerfile Normal file
View File

@ -0,0 +1,34 @@
# Use an official Python runtime as a parent image
FROM python:3.13-slim-trixie
# Set environment variables
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
# Set working directory
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y \
build-essential \
libpq-dev \
default-libmysqlclient-dev \
pkg-config \
&& rm -rf /var/lib/apt/lists/*
# Install Python dependencies
COPY requirements.txt /app/
RUN pip install --no-cache-dir -r requirements.txt
RUN pip install gunicorn
# Copy project files
COPY . /app/
# Expose the port on which the application will run
EXPOSE 3030
# Define environment variable for Gunicorn
ENV GUNICORN_CMD_ARGS="--bind 0.0.0.0:3030"
# Run Gunicorn server with your Django application
CMD ["gunicorn", "BH.wsgi:application"]

View File

@ -1,18 +1,30 @@
## Requirements
The website requires the following:
- Docker
- Docker Compose
- A running MariaDB Instance
## Deployment ## Deployment
To deploy the website: To deploy the website:
- first clone the repository to any local folder - first clone the repository to any local folder
- then open the folder with the `manage.py` file - then open the folder
- then `Shift`+`Right Click` and click on `Open PowerShell window here` - create a new file called `.env` and add the following:
- then type in `py manage.py runserver 4000` ```
- open browser at address `localhost:4000` or `127.0.0.1:4000` SECRET_KEY=your_secret_key
DB_NAME=your_db_name
DB_USER=your_db_user
DB_PASSWORD=your_db_password
DB_HOST=your_db_host
DB_PORT=your_db_port
```
- run `docker-compose up --build` this starts the website at localhost:3030
## Structure ## Structure
The website has a Home page and a News page page currently. The News page shows all developer blogs. The website has a Home page and a News page currently. The News page shows all developer blogs.
## Problems and Development ## Problems and Development
Currently there are the following problems: Currently, there are the following problems:
- `/dev/` route needs to be made - the page needs a **Dark Mode**
- the Blog card needs to be updated - Ability for staff to add blogs directly
- the `secret` needs to be secret
If you find any problems and have a solution *in code* then please consider making a `pull request`, using the `Create a new branch for this commit and start a pull request` option with an appropriate branch name. If you find any problems and have a solution *in code* then please consider making a `pull request`, using the `Create a new branch for this commit and start a pull request` option with an appropriate branch name.

View File

View File

@ -1,15 +0,0 @@
from .views import *
from users.models import *
def is_action_processable(request):
user = Users.objects.get(request.body["user"])
if user:
# TODO implement
return True
def process_action(request):
if not is_action_processable(request):
return
print(request.body)

View File

@ -1,9 +0,0 @@
from django.contrib import admin
from .models import *
# Register your models here.
admin.site.register(Province)
admin.site.register(AssaultTroop)
admin.site.register(Player)
admin.site.register(Server)

View File

@ -1,5 +0,0 @@
from django.apps import AppConfig
class APIConfig(AppConfig):
name = 'api'

View File

@ -1,19 +0,0 @@
# Defines Data ffs
VIEW_ACTION = 0
PURCHASE_ACTION = 1
MODIFY_ACTION = 2
EQUIP_ACTION = 3
# Need to define costs for purchase actions and requirements for modify and equip actions
# Saving space and reducing response times of the server is a priority so keeping in mind that this
# data will probably live on the RAM (as file-io is costly) so it needs to be small and easy to parse
# # sights,trg,brl
# # cost,ammo^inter^ >^skin,number
# # |----|^^|-||-|>^>^|--||------|
# WEAPON_DATA = """000000000000000000000000000000""" # Use Ints goddamit
# oh yeah, ints
WEAPON_DATA = [] # wtf

View File

@ -1,8 +0,0 @@
from rest_framework.filters import BaseFilterBackend
from users.models import *
class UserDataFilterBackend(BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
queryset = queryset.filter(user__id=request.user.id)
return queryset

View File

@ -1,71 +0,0 @@
# Generated by Django 5.2.9 on 2026-02-19 10:09
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Player',
fields=[
('id', models.IntegerField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=50)),
('faction', models.IntegerField()),
('server', models.CharField(max_length=20)),
],
options={
'ordering': ['id'],
},
),
migrations.CreateModel(
name='Province',
fields=[
('id', models.IntegerField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=100)),
('faction', models.IntegerField()),
('map', models.CharField(max_length=255)),
('mov_speed', models.IntegerField()),
('ats', models.JSONField(null=True)),
],
options={
'ordering': ['id'],
},
),
migrations.CreateModel(
name='Server',
fields=[
('id', models.IntegerField(primary_key=True, serialize=False)),
('players', models.IntegerField()),
('capacity', models.IntegerField()),
('region', models.CharField(max_length=3)),
('address', models.CharField(max_length=20)),
],
options={
'ordering': ['id'],
},
),
migrations.CreateModel(
name='AssaultTroop',
fields=[
('id', models.IntegerField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=100)),
('faction', models.IntegerField()),
('type', models.IntegerField()),
('province', models.IntegerField()),
('orders', models.JSONField(null=True)),
('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ats', to=settings.AUTH_USER_MODEL)),
],
options={
'ordering': ['id'],
},
),
]

View File

@ -1,70 +0,0 @@
from django.db import models
from django.contrib.auth.models import User
from django.db.models.fields import CharField, IntegerField
# Create your models here.
class Province(models.Model):
id = models.IntegerField(primary_key = True)
name = models.CharField(max_length = 100, blank = False)
faction = models.IntegerField() # 0 - Neutral, 1 - Allies, 2 - Axis
map = models.CharField(max_length=255)
mov_speed = models.IntegerField()
ats = models.JSONField(null=True)
class Meta:
ordering = ['id']
def __str__(self):
return f"{self.name} - {self.faction}"
class AssaultTroop(models.Model):
id = models.IntegerField(primary_key = True)
name = models.CharField(max_length = 100, blank = False)
faction = models.IntegerField() # 0 - Neutral, 1 - Allies, 2 - Axis
type = models.IntegerField()
province = models.IntegerField() # Province ID (-1 for not deployed)
orders = models.JSONField(null=True)
owner = models.ForeignKey(
'auth.User',
related_name='ats',
on_delete=models.CASCADE
)
class Meta:
ordering = ['id']
def __str__(self):
return f"{self.name} - {self.province},{self.faction}"
class Player(models.Model):
id = models.IntegerField(primary_key = True)
name = models.CharField(max_length = 50, blank = False)
faction = models.IntegerField()
server = CharField(max_length = 20, blank = False)
class Meta:
ordering = ['id']
def __str__(self):
return self.name
class Server(models.Model):
id = IntegerField(primary_key = True)
players = IntegerField() # total current players
capacity = IntegerField() # max player capacity
region = CharField(max_length = 3, blank = False) # 3 letter abb. for region
address = CharField(max_length = 20, blank = False)
class Meta:
ordering = ['id']
def __str__(self):
return f"{self.address} - {self.region}"

View File

@ -1,24 +0,0 @@
from rest_framework import permissions
class IsSuperUserOrReadOnly(permissions.BasePermission):
def has_permission(self, request, view):
if request.method in permissions.SAFE_METHODS:
return True
return request.user.is_superuser
class IsStaff(permissions.BasePermission):
def has_permission(self, request, view):
return request.user.is_staff
class IsSuperUserOrAuthReadOnly(permissions.BasePermission):
def has_permission(self, request, view):
if request.method in permissions.SAFE_METHODS:
return request.user != None
return request.user.is_superuser
class IsSuperUser(permissions.BasePermission):
def has_permission(self, request, view):
return request.user.is_superuser

View File

@ -1,25 +0,0 @@
from rest_framework import serializers
from .models import *
class ProvinceSerializer(serializers.ModelSerializer):
class Meta:
model = Province
fields = ['id', 'name', 'faction', 'map', 'mov_speed', 'ats']
class AssaultTroopSerializer(serializers.ModelSerializer):
class Meta:
model = AssaultTroop
fields = ['id', 'name', 'faction', 'type', 'province', 'orders', 'owner']
class PlayerSerializer(serializers.ModelSerializer):
class Meta:
model = Player
fields = ['id', 'name', 'faction', 'server']
class ServerSerializer(serializers.ModelSerializer):
class Meta:
model = Server
fields = ['id', 'players', 'capacity', 'region', 'address']

View File

@ -1,12 +0,0 @@
from django import template
from django.template.defaultfilters import stringfilter
import markdown as md
register = template.Library()
@register.filter()
@stringfilter
def markdown(value):
return md.markdown(value, extensions=['markdown.extensions.fenced_code'])

View File

@ -1,72 +0,0 @@
from django.test import TestCase
from django.contrib.auth.models import User
from rest_framework.test import APITestCase, APIClient
from rest_framework import status
# Create your tests here.
class UnAuthAccessTests(APITestCase):
def test_province_get(self):
response = self.client.get("/api/v1/provinces/")
self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_assault_troops_get(self):
response = self.client.get("/api/v1/assault_troops/")
self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_servers_get(self):
response = self.client.get("/api/v1/servers/")
self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_players_get(self):
response = self.client.get("/api/v1/players/")
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
def test_user_action_get(self):
response = self.client.get("/api/v1/user_action/")
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
def test_user_action_post(self):
response = self.client.post("/api/v1/user_action/", {"user": 1, "action": "142"}, format="json")
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
class AuthAccessTests(APITestCase):
def setUp(self):
self.user = User.objects.create_user(username="test", password="pass123")
self.user2 = User.objects.create_user(username="test2", password="pass123", is_staff=True)
def test_province_get(self):
self.client.login(username="test", password="pass123")
response = self.client.get("/api/v1/provinces/")
self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_assault_troops_get(self):
self.client.login(username="test", password="pass123")
response = self.client.get("/api/v1/assault_troops/")
self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_servers_get(self):
self.client.login(username="test", password="pass123")
response = self.client.get("/api/v1/servers/")
self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_players_get(self):
self.client.login(username="test", password="pass123")
response = self.client.get("/api/v1/players/")
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
def test_players_staff_get(self):
self.client.login(username="test2", password="pass123")
response = self.client.get("/api/v1/players/")
self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_user_action_post(self):
self.client.login(username="test", password="pass123")
response = self.client.post("/api/v1/user_action/", {"user": 1, "action": "142"}, format="json")
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
def test_user_data_get(self):
self.client.login(username="test", password="pass123")
response = self.client.get("/api/v1/user_data/")
self.assertEqual(response.status_code, status.HTTP_200_OK)

View File

@ -1,16 +0,0 @@
from django.urls import path
from .views import *
urlpatterns = [
path('provinces/', ProvincesView.as_view()),
path('provinces/<int:pk>/', ProvinceView.as_view()),
path('assault_troops/', AssaultTroopsView.as_view()),
path('assault_troops/<int:nm>/', AssaultTroopView.as_view()),
path('players/', PlayersView.as_view()),
path('players/<int:nm>/', PlayerView.as_view()),
path('servers/', ServersView.as_view()),
path('servers/<int:nm>/', ServerView.as_view()),
path('user_data/', UserDataView.as_view()),
path('user_data/<int:pk>/', UserDatumView.as_view()),
path('user_action/', UserActionView.as_view()),
]

View File

@ -1,87 +0,0 @@
from rest_framework import generics, permissions, mixins
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator
from .models import *
from .filters import *
from .actions import *
from .serializers import *
from .permissions import *
from users.models import *
from users.serializers import *
@method_decorator(csrf_exempt, name='dispatch')
class ProvincesView(generics.ListCreateAPIView):
permission_classes = (IsSuperUserOrReadOnly,)
queryset = Province.objects.all()
serializer_class = ProvinceSerializer
@method_decorator(csrf_exempt, name='dispatch')
class ProvinceView(generics.RetrieveUpdateDestroyAPIView):
permission_classes = (IsSuperUserOrReadOnly,)
queryset = Province.objects.all()
serializer_class = ProvinceSerializer
@method_decorator(csrf_exempt, name='dispatch')
class AssaultTroopsView(generics.ListCreateAPIView):
permission_classes = (IsSuperUserOrReadOnly,)
queryset = AssaultTroop.objects.all()
serializer_class = AssaultTroopSerializer
@method_decorator(csrf_exempt, name='dispatch')
class AssaultTroopView(generics.RetrieveUpdateDestroyAPIView):
permission_classes = (IsSuperUserOrReadOnly,)
queryset = AssaultTroop.objects.all()
serializer_class = AssaultTroopSerializer
@method_decorator(csrf_exempt, name='dispatch')
class PlayersView(generics.ListCreateAPIView):
permission_classes = (IsStaff,) # Only Staff can see player info, i.e. authorized servers
queryset = Player.objects.all()
serializer_class = PlayerSerializer
@method_decorator(csrf_exempt, name='dispatch')
class PlayerView(generics.RetrieveUpdateDestroyAPIView):
permission_classes = (IsStaff,)
queryset = Player.objects.all()
serializer_class = PlayerSerializer
@method_decorator(csrf_exempt, name='dispatch')
class ServersView(generics.ListCreateAPIView):
permission_classes = (IsSuperUserOrReadOnly,)
queryset = Server.objects.all()
serializer_class = ServerSerializer
@method_decorator(csrf_exempt, name='dispatch')
class ServerView(generics.RetrieveUpdateDestroyAPIView):
permission_classes = (IsSuperUserOrReadOnly,)
queryset = Server.objects.all()
serializer_class = ServerSerializer
@method_decorator(csrf_exempt, name='dispatch')
class UserDatumView(generics.RetrieveUpdateDestroyAPIView):
permission_classes = (IsSuperUser,)
queryset = UserData.objects.all()
serializer_class = UserDataSerializer
# filter_backends = [UserDataFilterBackend]
@method_decorator(csrf_exempt, name='dispatch')
class UserDataView(generics.ListCreateAPIView):
permission_classes = (IsSuperUserOrAuthReadOnly,)
queryset = UserData.objects.all()
serializer_class = UserDataSerializer
filter_backends = [UserDataFilterBackend]
@method_decorator(csrf_exempt, name='dispatch')
class UserActionView(mixins.CreateModelMixin, generics.GenericAPIView):
permission_classes = (permissions.IsAuthenticated,)
queryset = UserAction.objects.all()
serializer_class = UserActionSerializer
def post(self, request, *args, **kwargs):
process_action(request)
return self.create(request, *args, **kwargs)

View File

@ -1,9 +0,0 @@
from django import forms
from .models import Post
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ['content']

View File

@ -1,37 +0,0 @@
# Generated by Django 5.0.2 on 2024-07-06 16:24
import django.db.models.deletion
import django.utils.timezone
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Blog',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('content', models.TextField()),
('title', models.CharField(max_length=150)),
('date_posted', models.DateTimeField(default=django.utils.timezone.now)),
('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='Post',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('content', models.TextField()),
('date_posted', models.DateTimeField(default=django.utils.timezone.now)),
('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@ -1,23 +0,0 @@
# Generated by Django 5.2.9 on 2026-02-19 10:09
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('blog', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='blog',
name='id',
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
),
migrations.AlterField(
model_name='post',
name='id',
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
),
]

View File

@ -1,22 +1,14 @@
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db import models from django.db import models
# from django.utils import timezone from django.utils import timezone
# Create your models here. # Create your models here.
class Blog(models.Model): class Blog(models.Model):
content = models.TextField() content = models.TextField()
title = models.CharField(max_length=150) title = models.CharField(max_length=150)
author = models.ForeignKey(User, on_delete=models.CASCADE) author = models.TextField() #models.ForeignKey(User, on_delete=models.CASCADE)
# date_posted = models.DateTimeField(default=timezone.now) date_posted = models.DateTimeField(default=timezone.now)
date_posted = models.DateTimeField(auto_now_add=True)
def get_absolute_url(self): def get_absolute_url(self):
return '/' return '/'
class Post(models.Model):
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
date_posted = models.DateTimeField(auto_now_add=True)

View File

@ -4,12 +4,7 @@ from . import views
urlpatterns = [ urlpatterns = [
path('', views.home, name='Home'), path('', views.home, name='Home'),
path('news/', views.news, name='News'), path('news/', views.news, name='News'),
# path('blog/<int:pk>', views.BlogDetailView.as_view(), name='Blog'), path('blog/<int:pk>', views.BlogDetailView.as_view(), name='Blog'),
# path('blog/create/', views.BlogCreateView.as_view(), name='Blog Create'), path('dev/', views.dev, name='Dev'),
# path('post/<int:pk>', views.PostDetailView.as_view(), name='Post'),
# path('post/create/', views.PostCreateView.as_view(), name='Post Create'),
# path('dev/', views.dev, name='Dev'),
path('chat/', views.chat, name='Chat'),
path("chat/p/", views.posts_partial, name="ChatPartial"),
path('dev/support/', views.support, name='Support'), path('dev/support/', views.support, name='Support'),
] ]

View File

@ -1,11 +1,9 @@
from django.db.models import QuerySet
from django.shortcuts import render from django.shortcuts import render
from django.contrib.auth.mixins import * from django.contrib.auth.mixins import *
from django.views.generic import * from django.views.generic import *
from .models import * from .models import *
from .forms import *
MAX_POSTS = 150
MARGIN = 20
# Create your views here. # Create your views here.
def news(request): def news(request):
@ -45,70 +43,13 @@ class BlogDetailView(DetailView):
template_name = 'blog/blogDetail.html' template_name = 'blog/blogDetail.html'
class BlogCreateView(LoginRequiredMixin, CreateView):
model = Blog
template_name = 'blog/blogCreate.html'
fields = ['title', 'content']
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form) and self.request.user.is_staff
class PostDetailView(DetailView):
model = Post
template_name = 'blog/postDetail.html'
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
template_name = 'blog/postCreate.html'
fields = ['content']
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
def chat(request):
if request.method == "POST":
form = PostForm(request.POST)
if form.is_valid():
post = form.save(commit=False)
post.author = request.user
post.save()
print(Post.objects.count())
if Post.objects.count() > MAX_POSTS + MARGIN:
qs = Post.objects.order_by("-date_posted")
old_ids = qs.values_list("id", flat=True)[MAX_POSTS:]
if old_ids:
Post.objects.filter(id__in=old_ids).delete()
return render(request, "blog/partials/post.html", {"post": post})
posts = Post.objects.select_related("author").order_by("-date_posted")
form = PostForm()
return render(request, "blog/postList.html", {
"posts": posts,
"form": form
})
def posts_partial(request):
posts = Post.objects.select_related("author").order_by("-date_posted")[:40]
return render(request, "blog/partials/postList.html", {
"posts": posts
})
def dev(request): def dev(request):
return render(request, 'dev.html', {'title': 'Development'}) return render(request, 'dev.html', {'title': 'Development'})
def support(request): def support(request):
return render(request, 'support.html', {'title': 'Support Us'}) return render(request, 'support.html', {'title': 'Support Us'})
def home(request): def home(request):
return render(request, 'index.html', {'title': 'Home'}) return render(request, 'index.html', {'title': 'Home'})

Binary file not shown.

34
docker-compose.yml Normal file
View File

@ -0,0 +1,34 @@
services:
db:
image: mariadb:12.2
env_file:
- .env
environment:
MYSQL_DATABASE: ${DB_NAME}
MYSQL_USER: ${DB_USER}
MYSQL_PASSWORD: ${DB_PASSWORD}
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
volumes:
- mariadb_data:/var/lib/mysql
restart: unless-stopped
django:
build: .
env_file:
- .env
volumes:
- .:/app
restart: unless-stopped
ports:
- "3030:3030"
depends_on:
- db
command: >
sh -c "python manage.py makemigrations blog --noinput &&
python manage.py migrate --noinput &&
python manage.py collectstatic --noinput &&
gunicorn BH.wsgi:application --bind 0.0.0.0:3030 --workers ${GUNICORN_WORKERS:-3}"
volumes:
mariadb_data:

BIN
media/Asset 2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
media/Asset 3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

BIN
media/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
media/mittelschrift.ttf Normal file

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

View File

@ -1,3 +0,0 @@
@ echo off
echo The devlopment server will start in a few seconds...
python manage.py runserver 3000

View File

@ -3,536 +3,35 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Beyond Heroes - Official Website</title> <title>Beyond Heroes</title>
<!-- <link rel="stylesheet" href="demo3.css"> --> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<link rel="preconnect" href="https://fonts.googleapis.com"> <style>
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;700&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Mozilla+Headline" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style type="text/css">
:root {
--primary-color: #009020; /* Accents */
--primary-dark-color: #006020; /* Secondary Accents */
--secondary-color: #333; /* Dark gray for text */
--background-color: #1a1a1a; /* Dark background */
--card-bg-color: #2a2a2a; /* Background for cards */
/* --font-family: 'Montserrat', sans-serif;*/
}
@font-face { @font-face {
font-family: 'Alte DIN 1451 Mittelschrift'; font-family: 'Alte DIN 1451 Mittelschrift';
src: url('mittelschrift.ttf') format('truetype'); /* Chrome 4+, Firefox 3.5, Opera 10+, Safari 3—5 */ src: url('/media/mittelschrift.ttf') format('truetype'); /* Chrome 4+, Firefox 3.5, Opera 10+, Safari 3—5 */
} }
body { body {
font-family: 'Alte DIN 1451 Mittelschrift', sans-serif; font-family: 'Alte DIN 1451 Mittelschrift', sans-serif;
} }
/* Allgemeine Stile (General Styles) */
body {
margin: 0;
/* font-family: var(--font-family);*/
color: white;
background-color: var(--background-color);
line-height: 1.6;
}
h1, h2 {
font-family: 'Mozilla Headline';
letter-spacing: -1px;
margin: 0;
}
h3 {
margin: 0;
}
a {
color: var(--primary-color);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
section {
padding: 60px 20px;
}
hr {
border: 0;
border-top: 1px solid #444;
margin: 15px 0;
}
/* Hero-Bereich (Hero Section) */
.hero-section {
position: relative;
height: 100vh; /* Volle Bildschirmhöhe (Full viewport height) */
background-size: cover;
background-position: center;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
color: white;
}
.hero-content {
background-color: rgba(0, 0, 0, 0.8);
padding: 40px;
margin: 0px 20%;
border-radius: 10px;
}
.hero-section h1 {
font-size: 4.7em;
font-weight: bold;
margin-block-start: 0;
margin-block-end: 0;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.7);
}
.hero-section p {
margin-block-start: 0;
font-size: 1.2em;
font-weight: lighter;
margin-bottom: 30px;
}
.discord-button {
background-color: var(--primary-color);
color: var(--background-color);
padding: 15px 30px;
letter-spacing: +0.8px;
border-radius: 50px;
font-weight: bold;
transition: background-color 0.3s ease;
}
.discord-button:hover {
background-color: var(--primary-dark-color);
text-decoration: none;
}
/* Team-Bereich (Team Section) */
.team-container {
display: flex;
gap: 40px;
max-width: 1200px;
margin: auto;
align-items: flex-start;
}
.dev-cards-column {
flex: 6;
display: flex;
flex-direction: column;
gap: 15px;
}
.dev-card {
display: flex;
align-items: center;
gap: 20px;
background-color: var(--card-bg-color);
padding: 15px;
border-radius: 8px;
cursor: pointer;
transition: background-color 0.3s ease;
}
.dev-card:hover, .dev-card.active {
background-color: var(--primary-color);
color: var(--background-color);
}
.dev-card:hover .dev-info p, .dev-card.active .dev-info p {
color: var(--background-color);
}
.dev-card img {
border-radius: 50%;
width: 60px;
height: 60px;
object-fit: cover;
}
.dev-info h3 {
margin-block-end: 0;
}
.dev-info p {
margin-block-start: 0;
margin-block-end: 0;
font-style: italic;
color: #bbb;
}
.dev-bio-column {
flex: 10;
}
#dev-bio-box {
background-color: var(--card-bg-color);
padding: 30px;
border-radius: 8px;
min-height: 975px; /* Stellt sicher, dass die Box groß genug ist (Ensures the box is large enough) */
}
#dev-bio-box p {
margin-block-start: 5px;
margin-block-end: 0;
font-size: 1.2em;
}
#dev-bio-box h3 {
color: var(--primary-color);
margin-block-end: 0;
font-size: 1.5em;
}
/* NEUE NAV-BAR (NEW NAV BAR) */
#navbar {
position: absolute;
top: 0;
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 00px;
z-index: 1000;
transition: background-color 0.3s ease, padding 0.3s ease;
}
#navbar .nav-brand{
margin-left: 40px;
}
#navbar .nav-links-div{
margin-right: 40px;
}
#navbar.scrolled {
position: fixed;
padding: 20px 40px;
top: 0;
background-color: var(--background-color); /* Feste Hintergrundfarbe (Solid background color) */
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.5);
padding: 10px 40px;
}
#navbar.scrolled .nav-brand{
margin-left: 0px;
}
#navbar.scrolled .nav-links-div{
margin-right: 80px;
}
.nav-brand {
font-weight: bold;
font-size: 1.5em;
color: white;
}
.nav-links {
list-style: none;
letter-spacing: +0.6px;
display: flex;
gap: 30px;
margin: 0;
padding: 0;
}
.nav-links a {
color: white;
font-weight: bold;
transition: color 0.3s ease;
}
.nav-links button {
color: white;
font-weight: bold;
transition: color 0.3s ease;
background: transparent;
box-shadow: 0 0 0 #000;
border-width: 0;
font-size: 1em;
padding: 0px;
}
.nav-links a:hover, button:hover{
color: var(--primary-color);
text-decoration: none;
}
/* Responsive Design (Für kleinere Bildschirme) (For smaller screens) */
@media (max-width: 768px) {
.hero-section h1 {
font-size: 2.5em;
}
.hero-content p {
font-size: 1em;
}
.news-container, .team-container {
grid-template-columns: 1fr;
flex-direction: column;
}
.dev-bio-column {
min-height: auto;
}
}
/* NEUE ALLGEMEINE HILFSKLASSE (NEW GENERAL HELPER CLASS) */
.text-center {
text-align: center;
}
/* NEUER SUPPORT-BEREICH (NEW SUPPORT SECTION) */
#support-section {
/* margin-top: 90px;*/
padding: 80px 20px;
/* background-color: var(--card-bg-color);*/
}
#support-section h2 {
color: var(--primary-color);
font-size: 2.5em;
margin-bottom: 40px;
color: #eee;
}
#support-section h3 {
margin: 20px;
color: #ddd;
}
#support-section p {
max-width: 800px;
margin: auto;
font-size: 1.1em;
color: #bbb;
}
/* NEUER SUPPORT-BEREICH (NEW SUPPORT SECTION) */
#about-section {
padding: 80px 20px;
background-color: var(--card-bg-color);
}
#about-section h2 {
color: var(--primary-color);
font-size: 2.5em;
margin-bottom: 40px;
}
#about-section h3 {
margin: 20px;
}
#about-section p {
max-width: 800px;
margin: auto;
font-size: 1.1em;
color: #ccc;
}
/* Fußzeile (Footer) */
footer {
background-color: #111;
text-align: center;
padding: 30px;
margin-top: 50px;
font-size: 0.9em;
color: #bbb;
}
footer p {
margin: 0;
}
.footer-socials {
margin-bottom: 5px;
}
.footer-socials a {
color: #fff;
font-size: 2em; /* Größe der Icons (Size of the icons) */
margin: 0 50px;
transition: color 0.3s ease;
}
.footer-socials a:hover {
color: var(--primary-color);
}
/* Neuigkeiten-Bereich (News Section) */
#news-section h2, #team-section h2 {
text-align: center;
margin-bottom: 40px;
font-size: 2.5em;
color: var(--primary-color);
}
/* NEUE STILE FÜR DEN NEUIGKEITEN-BEREICH (NEW STYLES FOR NEWS SECTION) */
.news-container {
display: flex;
justify-content: center;
gap: 30px;
max-width: 1200px;
margin: auto;
flex-wrap: wrap; /* Erlaubt das Umbrechen auf kleineren Bildschirmen (Allows wrapping on smaller screens) */
}
.news-post {
background-color: var(--card-bg-color);
border-radius: 8px;
overflow: hidden; /* Stellt sicher, dass das Bild innerhalb der Form bleibt (Ensures the image stays within the shape) */
width: 350px; /* Feste Breite für jede Karte (Fixed width for each card) */
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.4);
transition: transform 0.3s ease;
}
.news-post:hover {
transform: translateY(-5px);
}
.news-post img {
width: 100%;
height: 200px;
object-fit: cover;
display: block;
}
.post-content {
padding: 20px;
}
.news-post h3 {
color: var(--primary-color);
margin-block-end: 0;
font-size: 1.5em;
}
.news-post p {
font-size: 1.1em;
}
.news-post p.news-date {
margin-block-start: 0;
margin-block-end: 0;
font-style: italic;
font-size: 0.9em;
color: #bbb;
margin-bottom: 15px;
}
.news-post .read-more {
display: inline-block;
color: var(--primary-color);
font-weight: bold;
margin-top: 10px;
}
.news-post .read-more:hover {
text-decoration: underline;
}
/* ... Der Rest des CSS-Codes bleibt gleich ... */
/* Responsive Design (Für kleinere Bildschirme) */
@media (max-width: 768px) {
/* ... Die bereits existierenden Media-Query-Styles bleiben bestehen ... */
.news-container {
flex-direction: column;
align-items: center;
}
.news-post {
width: 90%; /* Nimmt fast die ganze Breite ein (Takes up almost the full width) */
max-width: 400px;
}
}
/*********************************************************************************************/
/* NEUER SUPPORT-BEREICH (NEW SUPPORT SECTION) */
#supporters-carousel {
padding-top: 50px;
text-align: center;
/* background-color: var(--card-bg-color);*/
}
#supporters-carousel h3 {
color: white;
font-size: 2em;
margin-bottom: 30px;
}
.carousel-container {
overflow: hidden;
white-space: nowrap;
position: relative;
max-width: 100%;
}
.carousel-content {
display: inline-block;
animation: scroll-left 20s linear infinite; /* Animation für das Karussell (Animation for the carousel) */
}
.supporter-item {
display: inline-block;
background-color: var(--card-bg-color);
border: 1px solid var(--primary-color);
color: white;
padding: 15px 25px;
border-radius: 8px;
margin: 0 10px;
text-align: center;
min-width: 150px;
}
.supporter-item h4 {
margin: 0;
font-size: 1.2em;
}
.supporter-item p {
margin: 5px 0 0;
font-size: 1.1em;
font-weight: bold;
color: var(--primary-color);
}
/* Animation Keyframes für das Scrollen (Animation keyframes for scrolling) */
@keyframes scroll-left {
0% { transform: translateX(0); }
100% { transform: translateX(-50%); }
}
</style> </style>
</head> </head>
<body> <body>
<nav class="navbar navbar-expand-lg navbar-dark" style="background-color: #000;">
<nav id="navbar"> <a class="navbar-brand" href="/" style="font-size: 24px;margin-left: 20px;">
<div class="nav-brand">Beyond Heroes</div> <img src="/media/Asset 3.png" alt="BH Logo" height="30" style="padding-right: 2px;"> Beyond Heroes
<div class="nav-links-div"> </a>
<ul class="nav-links"> <div class="ml-auto"></div>
{% if user.is_authenticated %} <div class="justify-content-center" style="margin-right: 20px;" id="navbarNav">
<li><a href="/#news-section">News</a></li> <ul class="navbar-nav">
<li><a href="{% url 'Chat' %}">Chat</a></li> <li class="nav-item" style="padding-right: 10px; padding-left: 10px;">
{% else %} <a class="nav-link" href="/news">News</a>
<li><a href="/#hero-section">Home</a></li>
<li><a href="/#about-section">About</a></li>
{% endif %}
<li><a href="/dev/support">Support Us</a></li>
{% if user.is_authenticated %}
<li class="nav-item">
<form action="{% url 'Logout' %}" method="post">
{% csrf_token %}
<button class="nav-link" type="submit">Logout</button>
</form>
</li> </li>
{% else %} <li class="nav-item" style="padding-right: 10px; padding-left: 10px;">
<!-- <li class="nav-item"> <a class="nav-link" href="/dev/">Development</a>
<a class="nav-link" href="{% url 'Login' %}">Login</a> </li>
<li class="nav-item" style="padding-right: 10px; padding-left: 10px;">
<a class="nav-link" href="/dev/support/">Support Us</a>
</li> </li>
<li class="nav-item">
<a class="nav-link" href="{% url 'Register' %}">Register</a>
</li> -->
{% endif %}
</ul> </ul>
</div> </div>
</nav> </nav>
@ -541,34 +40,14 @@
{% endblock %} {% endblock %}
<div style="padding: 30px;"></div>
<footer> <footer>
<div class="footer-socials"> <div class="container">
<a href="https://www.youtube.com/@BeyondHeroesWWII/" target="blank" aria-label="YouTube"><i class="fab fa-youtube"></i></a> <!-- <p style="text-align: center;">&copy; Beyond Heroes. All rights reserved.</p> -->
<a href="https://discord.gg/gnnfKKuumg" target="blank" aria-label="Discord"><i class="fab fa-discord"></i></a>
<!-- <a href="#" aria-label="Twitter"><i class="fab fa-twitter"></i></a> -->
<a href="https://www.reddit.com/r/beyondheroes/" target="blank" aria-label="Reddit"><i class="fab fa-reddit"></i></a>
</div> </div>
<p style="padding-top:10px">Beyond Heroes™ | 2026 All Rights Reserved.</p>
</footer> </footer>
<!-- Bootstrap JavaScript and dependencies -->
<script type="text/javascript"> <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.6/dist/umd/popper.min.js"></script>
const navbar = document.getElementById('navbar'); <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.min.js"></script>
const heroSection = document.querySelector('.hero-section');
if (heroSection == null) {
navbar.classList.add('scrolled');
}
else {
window.addEventListener('scroll', () => {
const heroHeight = heroSection.offsetHeight;
if (window.scrollY > heroHeight - 70) {
navbar.classList.add('scrolled');
} else {
navbar.classList.remove('scrolled');
}
});
}
</script>
</body> </body>
</html> </html>

View File

@ -1,42 +0,0 @@
{% extends 'base.html' %}
{% block content %}
<h1 class="display-3" style="font-family: rockwell;"><strong>TechBlog</strong></h1>
<h4 class="lead"><strong>We provide a platform for people who want to help others.</strong></h4>
<hr>
<br>
<ul style="margin-bottom: 100px">
{% for blog in blog %}
<!-- <li>-->
<!-- <h2>{{ blog.title }}</h2>-->
<!-- <p>{{ blog.content }}</p>-->
<!-- <hr>-->
<!-- </li>-->
<blockquote class="blockquote">
<a href="{% url 'Blog' blog.id %}" class="h2" style="font-family: rockwell; color:rgb(0, 92, 167);">{{ blog.title }}</a>
<p class="mb-0">{{ blog.content }}</p>
<footer class="blockquote-footer">posted by <cite title="{{ blog.author }}"><a href="{% url 'NamedProfile' blog.author.id %}">{{ blog.author }}</a></cite> on <cite title="{{ blog.date_posted }}">{{ blog.date_posted|date:"M j, o" }}</cite></footer>
</blockquote>
<hr>
<!-- <li class="media">-->
<!-- <img src="..." class="mr-3" alt="...">-->
<!-- <div class="media-body">-->
<!-- <h5 class="mt-0 mb-1">{{ blog.title }}</h5>-->
<!-- {{ blog.content }}-->
<!-- </div>-->
<!-- </li>-->
<!-- <hr>-->
<!-- <br>-->
<!-- <div class="card">-->
<!-- <div class="card-header">-->
<!-- {{ blog.author }}-->
<!-- </div>-->
<!-- <div class="card-body">-->
<!-- <h5 class="card-title">{{ blog.title }}</h5>-->
<!-- <p class="card-text">{{ blog.content }}</p>-->
<!-- <a href="#" class="btn btn-primary">Read More</a>-->
<!-- </div>-->
<!-- </div>-->
<!-- <br>-->
{% endfor %}
</ul>
{% endblock %}

View File

@ -1,24 +0,0 @@
{% extends 'base.html' %}
{% block content %}
{% load crispy_forms_tags %}
<div class="container mt-5">
<form method="POST">
{% csrf_token %}
<div class="card mb-4">
<div class="card-header">
Create a Blog
</div>
<div class="card-body">
{{ form | crispy }}
<br>
<button class="btn btn-light" type="submit"> Post </button>
</div>
<!-- <div class="card-footer">
<small class="text-muted">
Don't Have An Account? <a class="link" href="{% url 'Register' %}">Register</a>
</small>
</div> -->
</div>
</form>
</div>
{% endblock %}

View File

@ -2,11 +2,18 @@
{% block content %} {% block content %}
{% load markdown_extras %} {% load markdown_extras %}
<br> <div class="container mt-5">
<blockquote class="blockquote">
<h2 class="h2" style="font-family: rockwell;">{{ object.title | markdown | safe }}</h2> <div class="card mb-3">
<p class="mb-0">{{ object.content | markdown | safe }}</p> <div class="card-body">
<footer class="blockquote-footer">posted by <cite title="{{ object.author }}"><a href="{% url 'NamedProfile' blog.author.id %}">{{ object.author }}</a></cite> on <cite title="{{ object.date_posted }}">{{ object.date_posted|date:"M j, o" }}</cite></footer> <h3 class="card-title">{{ object.title | markdown | safe }}</h3>
</blockquote> <p class="card-text">{{ object.content | markdown | safe }}</p>
<hr style="margin-bottom: 20px"> </div>
<div class="card-footer">
By <cite title="{{ object.author }}"></cite> on <cite title="{{ object.date_posted }}">{{ object.date_posted|date:"M j, o" }}</cite>
</div>
</div>
</div>
{% endblock %} {% endblock %}

View File

@ -1,49 +0,0 @@
{% extends 'base.html' %}
{% block content %}
{% load markdown_extras %}
<!-- Main Content -->
<div class="container mt-5">
<div class="row">
<div class="col-lg-8">
<!-- Feed -->
<div class="card">
<div class="card-header">
Latest Posts
</div>
<div class="card-body">
{% for blog in blogs %}
<!-- Posts will be dynamically added here -->
<div class="card mb-3">
<div class="card-body">
<h4 class="card-title"><a href="{% url 'Blog' blog.id %}" class="text-dark">{{ blog.title }}</a></h4>
<p class="card-text">{{ blog.content | markdown | safe }}</p>
</div>
</div>
<!-- End of Posts -->
{% endfor %}
</div>
</div>
</div>
<!-- Sidebar -->
<div class="col-lg-4">
<div class="card mb-4">
<div class="card-header">
Updates
</div>
<div class="card-body">
<ul class="list-group list-group-flush">
{% for topic in topics %}
<li class="list-group-item">{{ topic }}</li>
{% endfor %}
</ul>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -2,8 +2,6 @@
{% block content %} {% block content %}
{% load markdown_extras %} {% load markdown_extras %}
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<br><br>
<!-- Main Content --> <!-- Main Content -->
<div class="container mt-5"> <div class="container mt-5">
<div class="row"> <div class="row">
@ -19,7 +17,7 @@
<!-- Posts will be dynamically added here --> <!-- Posts will be dynamically added here -->
<div class="card mb-3"> <div class="card mb-3">
<div class="card-body"> <div class="card-body">
<h5 class="card-title">{{ post.title }} <a href="#" class="link text-secondary lead">@{{ post.author }}</a></h5> <h4 class="card-title"><a href="{% url 'Blog' post.id %}" class="text-dark">{{ post.title }}</a></h4>
<p class="card-text">{{ post.content | markdown | safe }}</p> <p class="card-text">{{ post.content | markdown | safe }}</p>
</div> </div>
</div> </div>

View File

@ -1,5 +0,0 @@
{% load markdown_extras %}
<div class="message">
<div class="message-author">{{ post.author.username }} <span style="font-size: 10px">{{ post.date_posted | date:"Y/m/d H:i" }}</span></div>
<p class="message-content">{{ post.content | markdown | safe }}</p>
</div>

View File

@ -1,3 +0,0 @@
{% for post in posts reversed %}
{% include "blog/partials/post.html" %}
{% endfor %}

View File

@ -1,24 +0,0 @@
{% extends 'base.html' %}
{% block content %}
{% load crispy_forms_tags %}
<div class="container mt-5">
<form method="POST">
{% csrf_token %}
<div class="card mb-4">
<div class="card-header">
Create a Post
</div>
<div class="card-body">
{{ form | crispy }}
<br>
<button class="btn btn-light" type="submit"> Post </button>
</div>
<div class="card-footer">
<small class="text-muted">
Don't Have An Account? <a class="link" href="{% url 'Register' %}">Register</a>
</small>
</div>
</div>
</form>
</div>
{% endblock %}

View File

@ -1,18 +0,0 @@
{% extends 'base2.html' %}
{% block content %}
{% load markdown_extras %}
<br>
<blockquote class="blockquote">
<p class="mb-0">{{ object.content | markdown | safe }}</p>
<footer class="blockquote-footer">posted by <cite title="{{ object.author }}"><a href="{% url 'NamedProfile' blog.author.id %}">{{ object.author }}</a></cite> on <cite title="{{ object.date_posted }}">{{ object.date_posted|date:"M j, o" }}</cite></footer>
</blockquote>
<hr style="margin-bottom: 20px">
{% for comment in object.comments %}
<blockquote class="blockquote" style="margin-left: 10px;">
<p class="mb-1">{{ comment.content | markdown | safe }}</p>
<footer class="blockquote-footer">posted by <cite title="{{ comment.author }}"><a href="{% url 'NamedProfile' comment.author.id %}">{{ comment.author }}</a></cite> on <cite title="{{ comment.date_posted }}">{{ comment.date_posted|date:"M j, o" }}</cite></footer>
</blockquote>
<hr style="margin-bottom: 10px; margin-left: 10px;">
{% endfor %}
{% endblock %}

View File

@ -1,121 +0,0 @@
{% extends 'base.html' %}
{% block content %}
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
<br><br><br>
<h2 style="opacity: 80%; padding-left:40px">General Chat</h2>
<style>
.chat-container{
/* width:420px;*/
height:470px;
border-radius:12px;
display:flex;
flex-direction:column;
overflow:hidden;
}
.messages{
flex:1;
overflow-y:auto;
padding: 10px 40px;
display:flex;
flex-direction:column;
gap:10px;
}
.message{
background:#334155;
padding:2px 5px;
border-radius:2px;
}
.message-author{
font-size:15px;
padding: 0px 5px;
opacity:0.7;
/* margin-bottom:4px;*/
}
p {
font-size: 18px;
padding: 0px 5px;
margin: 0;
}
.input-area{
display:flex;
height: 60px;
padding:20px 40px;
border-top:1px solid rgba(255,255,255,0.05);
}
.input-area input{
flex: 1;
padding:10px;
font-size: 30px;
border-radius:8px;
border:none;
outline:none;
background:#0f172a;
color:white;
}
.input-area button{
/* flex: 1;*/
margin-left:8px;
padding:10px 14px;
border:none;
border-radius:8px;
background:#3b82f6;
color:white;
cursor:pointer;
}
footer {;
margin-top: 10px;
}
</style>
<div class="chat-container">
<div
id="post-list"
class="messages"
hx-get="{% url 'ChatPartial' %}"
hx-trigger="every 3s"
hx-swap="innerHTML">
{% include "blog/partials/postList.html" %}
</div>
<form
class="input-area"
id="post-form"
hx-post="{% url 'Chat' %}"
hx-target="#post-list"
hx-swap="beforeend">
{% csrf_token %}
{{ form.content }}
<button type="submit">Send</button>
</form>
</div>
<script>
var container = document.getElementById("post-list");
let autoScroll = true
function isAtBottom(){
return container.scrollHeight - container.scrollTop - container.clientHeight < 50;
}
container.addEventListener("scroll", () => {
autoScroll = isAtBottom();
});
document.body.addEventListener("htmx:afterSwap", (e) => {
if(e.target.id !== "post-list") return
if(autoScroll){ container.scrollTop = container.scrollHeight; }
});
</script>
{% endblock %}

View File

@ -3,10 +3,10 @@
{% block content %} {% block content %}
<style type="text/css"> <style type="text/css">
body { body {
background-image: url('/media/bg.jpg'); background-size: cover; background-repeat: no-repeat; background-image: url('/media/bg.jpg'); background-size: cover; background-repeat: no-repeat;background-color: #000;
} }
</style> </style>
<div style="background-color: rgba(0, 0, 0, 0.6); color: #eeeeee; border-radius: 2px; margin-top: 10px;" class="container"> <div style="background-color: rgba(0, 0, 0, 0.6); color: #eeeeee; border-radius: 2px; margin-top: 10px; font-size: 18px;" class="container">
<div style="padding: 20px;"> <div style="padding: 20px;">
<h2 style="text-align: justify;">Beyond Heroes</h2> <h2 style="text-align: justify;">Beyond Heroes</h2>
<p style="text-align: justify;"> <p style="text-align: justify;">
@ -16,19 +16,13 @@
<div class="container"> <div class="container">
<h3 style="text-align: justify;">Our Developers</h3> <h3 style="text-align: justify;">Our Developers</h3>
<p style="text-align: justify;"> <p style="text-align: justify;">
Similique earum unde unde exercitationem totam voluptatibus. Cupiditate reiciendis aut ducimus sunt. Sunt laboriosam rerum qui maxime quod aut aspernatur. Ipsa reprehenderit quas nihil vel dignissimos at. Repellendus minima dolorem ipsa sit est ipsum accusamus.<br> Made up of enthusiastic helpers from all over the globe, with a strong core in Germany, Poland, France, India, Argentina, and more. Sharing ideas across so many cultures gives us fresh perspectives and helps us keep eyes on different parts that might be more relevant locally and their experience with other mmos. That way, we can reach farther and keep a foot in major countries around the globe. If you'd like to join, just <a href="https://forms.gle/wf4LTUHNW7hjPXcz6">fill out the form</a> and don't be scared to hit us up on Discord! Our work environment is super hostile and we do require you to have a sense of humor and the latest version of sarcasm which you can get here <a href="https://en.wikipedia.org/wiki/Sarcasm">Sarcasm-StopMakingNewBranches-win-x64.7z</a><br>
</p> </p>
</div> </div>
<div class="container"> <div class="container">
<h3 style="text-align: justify;">Our Vision</h3> <h3 style="text-align: justify;">Our Vision</h3>
<p style="text-align: justify;"> <p style="text-align: justify;">
Laborum nobis incidunt voluptate magnam voluptatibus. Itaque sequi eveniet vitae voluptas voluptatem ullam alias omnis. Officiis quia dolores architecto.Voluptate quia aspernatur excepturi voluptas molestias. Maxime magnam maiores sunt dolorum. Id possimus ullam rerum dignissimos.<br> So blurry the background image seems fine. Beyond Heroes, aims to create an exciting World War gaming experience inspired by 'Heroes & Generals' but without the issues it had. Our goal is to provide the same immersive and challenging game while renewing some changes and upgrading the technical systems that preceded. Through strategic gameplay that made HnG so interesting, War, and balanced mechanics, our game aims to deliver a fun and rewarding experience that appeals to old and new players. We value our community and strive to provide a respectful and enjoyable environment that fosters teamwork, camaraderie, and sportsmanship. Also this text sounds corporate af, and thus this anti-climatic sentence is here to break with the same old corporate speech that makes us pay 5$ to open the game. The team is mostly comprised of people who share the same core values those being: Being against the new trend of p2w mechanics, combating <a href="https://colinmagazine.com/blogs/news/live-service-fatigue-are-we-tired-of-battle-passes-microtransactions">microtransaction fatigue</a>, being in favour of <a href="https://www.stopkillinggames.com/">stop killing games</a>, being in favour of <a href="https://www.youtube.com/watch?v=2_Dtmpe9qaQ">clippy</a> and against anti-consumer practices <a href="https://www.forbes.com/sites/paultassi/2024/05/01/nba-2k-players-furious-about-kobe-bryant-collector-level-reward-removal/">(1)</a> <a href="https://www.gamesindustry.biz/epic-ea-roblox-and-more-face-eu-complaint-over-tricking-players-into-spending">(2)</a>, <a href="https://en.wikipedia.org/wiki/Enshittification">being absolutely against enshittification</a>, and in favor of remakes.<br>
</p>
</div>
<div class="container">
<h3 style="text-align: justify;">Roadmap</h3>
<p style="text-align: justify;">
A adipisci ad quia accusantium voluptates. Rem culpa et repellendus qui et tenetur quia. Nam rem provident non. Eum quod nostrum molestiae ut expedita vel. In voluptas ea quod. Modi incidunt qui tempora soluta minus odio sed et.Est aliquid id est quia praesentium rerum blanditiis. Voluptas a est omnis. Aut non quo non. Numquam quibusdam esse cum nostrum ab vel et. Fugit porro libero commodi dicta omnis libero.<br>
</p> </p>
</div> </div>
</div> </div>

View File

@ -1,62 +1,24 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% block content %} {% block content %}
<style type="text/css">
{% if not user.is_authenticated %} body {
<header class="hero-section" style="background-image: url('bg.png');"> background-image: url('/media/bg.jpg'); background-size: cover; background-repeat: no-repeat;background-color: #000;
<div class="hero-content"> }
<h1>BEYOND HEROES</h1> </style>
<p style="letter-spacing: +0.7px;">The project that rose from the ashes of a game that brought us all together. We aim to recreate the experience that we enjoyed throughout the years, while also adding new features that make this game truly <i>Beyond Heroes</i> and Generals.</p> <div style="position: absolute; bottom: 1; right: 0;margin: 25px; width: 35%; background-color: rgba(0, 0, 0, 0.6); color: #eeeeee; border-radius: 2px;">
<br> <div style="padding: 20px;">
<a href="{% url 'Register' %}" target="blank" class="discord-button">Join The Community</a> <h2 style="text-align: justify;">Beyond Heroes</h2>
<p style="padding-top: 20px; font-size: 1em">already a part? <a href="{% url 'Login' %}">Login</a></p> <p style="text-align: justify; font-size: 22px;">
</div> The project that rose from the ashes of a game that was once. We aim to recreate the experience that we enjoyed throughout the years, while also adding new features that make it truly <i>Beyond</i> Heroes and Generals.<br>
</header> Join Us on this journey to relive the old days and experience something new by clicking on the button below!
<br>
{% else %}
<section style="margin-top: 60px;" id="news-section">
<h2>DEVELOPMENT NEWS</h2>
<div class="news-container">
<article class="news-post">
<img src="https://picsum.photos/seed/news-1/400/250" alt="Gameplay Video">
<div class="post-content">
<h3>New 3D Models!</h3>
<p class="news-date">20 April 2025</p>
<p>Look at the great looking 3D Models made by our skilled modellers... <a href="#news-section">Read More</a></p>
</div>
</article>
<article class="news-post">
<img src="https://picsum.photos/seed/news-4/400/250" alt="Alpha-Test">
<div class="post-content">
<h3>Happy New Year!</h3>
<p class="news-date">01 January 2025</p>
<p>This was a great year and we have made good progress... <a href="#news-section">Read More</a></p>
</div>
</article>
<article class="news-post">
<img src="https://picsum.photos/seed/news-3/400/250" alt="Community Umfrage">
<div class="post-content">
<h3>New 3D Models!</h3>
<p class="news-date">26 November 2024</p>
<p>Look at the great looking 3D Models made by our skilled modellers... <a href="#news-section">Read More</a></p>
</div>
</article>
</div>
</section>
{% endif %}
<br>
<section id="about-section" class="text-center">
<h2>ABOUT US</h2>
<h3 style="text-align: justify-all; letter-spacing: +0.5px;">Our Developers</h3>
<p style="text-align: justify;">
Made up of enthusiastic helpers from all over the globe, with a strong core in Germany, Poland, France, India, Argentina, Checkia, Slovakia, Canada, and more. Sharing ideas across so many cultures gives us fresh perspectives and helps us keep eyes on different parts that might be more relevant locally and their experience with other mmos. That way, we can reach farther and keep a foot in major countries around the globe. If you'd like to join, just fill out the <a href="https://forms.gle/wf4LTUHNW7hjPXcz6" target="blank">form</a> and don't be scared to hit us up on Discord! Our work environment is super hostile and we do require you to have a sense of humor and the latest version of sarcasm which you can get here <a href="https://en.wikipedia.org/wiki/Sarcasm" target="blank">Sarcasm-StopMakingNewBranches-win-x64.7z</a>
</p> </p>
<h3 style="text-align: justify-all; letter-spacing: +0.5px;">Our Vision</h3> </div>
<p style="text-align: justify;"> </div>
Beyond Heroes, aims to create an exciting World War gaming experience inspired by 'Heroes & Generals' but without the issues it had. Our goal is to provide the same immersive and challenging game while renewing some changes and upgrading the technical systems that preceded. Through strategic gameplay that made HnG so interesting, War, and balanced mechanics, our game aims to deliver a fun and rewarding experience that appeals to old and new players. We value our community and strive to provide a respectful and enjoyable environment that fosters teamwork, camaraderie, and sportsmanship. Also this text sounds corporate af, and thus this anti-climatic sentence is here to break with the same old corporate speech that makes us pay 5$ to open the game. The team is mostly comprised of people who share the same core values those being: Being against the new trend of p2w mechanics, combating <a href="https://colinmagazine.com/blogs/news/live-service-fatigue-are-we-tired-of-battle-passes-microtransactions" target="blank">microtransaction fatigue</a>, being in favour of <a href="https://www.stopkillinggames.com/" target="blank">stop killing games</a>, being in favour of <a href="https://www.youtube.com/watch?v=2_Dtmpe9qaQ" target="blank">clippy</a> and against anti-consumer practices <a href="https://www.forbes.com/sites/paultassi/2024/05/01/nba-2k-players-furious-about-kobe-bryant-collector-level-reward-removal/" target="blank">(1)</a> <a href="https://www.gamesindustry.biz/epic-ea-roblox-and-more-face-eu-complaint-over-tricking-players-into-spending" target="blank">(2)</a>, being absolutely against <a href="https://en.wikipedia.org/wiki/Enshittification" target="blank">enshittification</a>, and in favor of remakes. <div style="position: absolute; bottom: 0; right: 0; margin-right: 25px; margin-bottom: 30px;">
</p> <a class="btn" href="https://discord.gg/gnnfKKuumg" style="padding: 10px 100px; font-family:'Trebuchet MS', sans-serif; font-size: 36px; font-weight: bold; background-color: #139358; color: white; border-radius: 2px;" onmouseover="this.style.backgroundColor='#0f7a4c'" onmouseout="this.style.backgroundColor='#139358'">
</section> To Battle!
</a>
</div>
{% endblock %} {% endblock %}

View File

@ -1,45 +1,44 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% block content %} {% block content %}
<br> <style type="text/css">
<section id="support-section" class="text-center"> body {
<h2>SUPPORT US</h2> background-image: url('/media/bg.jpg'); background-size: cover; background-repeat: no-repeat;background-color: #000;
<h3 style="text-align: justify-all; letter-spacing: +0.5px;">Hi! So, you want to support the project, I hear?</h3> }
/* Link-Farben definieren */
a.text-light {
color: #E2C490 !important;
}
a.text-light:visited {
color: #6c562d !important;
}
/* Optionale Hover-Effekte */
a.text-light:hover {
text-decoration: underline;
}
</style>
<div style="background-color: rgba(0, 0, 0, 0.6); color: #eeeeee; border-radius: 2px; margin-top: 10px;" class="container">
<div style="padding: 20px; font-size: 18px;">
<h2 style="text-align: justify;">Support Us</h2>
<hr style="background-color: #999999;">
<div class="container">
<h3 style="text-align: justify;">Hi! So, you want to support the project, I hear?</h3>
<p style="text-align: justify;"> <p style="text-align: justify;">
Well, the main way you can do that is by joining the team or telling people to join the team! You can fill out these forms if you're interested in any <a class="text-light" href="#">dev position</a> or if you're interested in <a class="text-light" href="#">translation</a>.<br> Well, the main way you can do that is by joining the team or telling people to join the team! You can fill out this form <a class="text-light" href="#"><strong>here</strong></a> if you're interested in any dev position or <a class="text-light" href="#"><strong>here</strong></a> if you're interested in translation.<br>
</p> </p>
<h3 style="text-align: justify-all; letter-spacing: +0.5px;">If you want to help economically... let's talk first...</h3> </div>
<div class="container">
<h3 style="text-align: justify;">If you want to help economically... let's talk first...</h3>
<p style="text-align: justify;"> <p style="text-align: justify;">
Here's the thing, I really appreciate that you want to help us out, and we LOVE your energy. We really don't want to accept economic help, and there are a few reasons as to why. Mostly, we can't (or don't want to) promise anything, we don't want to get your hopes up, and we don't want to lead you to believe in something that might not happen. Giving us money has a worse ROI than playing it all on red in roulette, and as such, to me personally, it feels like asking for money from you just because you like us. If that's the case, okay, alright, no problem; we still have to solve the distribution issue, but whatever, we'll get to it eventually. Otherwise, understand that the reason the links are so hidden is exactly because we don't want you to feel like you wasted money - at least not before you knowingly and willingly understand that this is just like giving it to us because you like us and want to, or you have money to spare and don't care. If you want to specifically help out a team member, assign it on the message or something so I know who not to give it to :P Here's the thing, I really appreciate that you want to help us out, and we LOVE your energy. We really don't want to accept economic help, and there are a few reasons as to why. Mostly, we can't (or don't want to) promise anything, we don't want to get your hopes up, and we don't want to lead you to believe in something that might not happen. Giving us money has a worse ROI than playing it all on red in roulette, and as such, to me personally, it feels like asking for money from you just because you like us. If that's the case, okay, alright, no problem; we still have to solve the distribution issue, but whatever, we'll get to it eventually. Otherwise, understand that the reason the links are so hidden is exactly because we don't want you to feel like you wasted money - at least not before you knowingly and willingly understand that this is just like giving it to us because you like us and want to, or you have money to spare and don't care. If you want to specifically help out a team member, assign it on the message or something so I know who not to give it to :P
<br> <br>
If you understand the above-mentioned stuff, then <a class="text-light" href="https://ko-fi.com/beyondheroes" target="blank"><strong>here's</strong></a> a link to our Ko-fi, and <a class="text-light" href="https://www.buymeacoffee.com/beyondheroes" target="blank"><strong>here's</strong></a> a link to our Buy Me a Coffee.
</p> </p>
<p style="text-align: justify; margin-top: 10px;">
If you understand the above-mentioned stuff, then here's the links to our <a class="text-light" href="https://ko-fi.com/beyondheroes" target="blank">Ko-fi</a>, and <a class="text-light" href="https://www.buymeacoffee.com/beyondheroes" target="blank">Buy Me a Coffee</a>.
</p>
<div id="supporters-carousel">
<h3>Special Thanks to...</h3>
<div class="carousel-container">
<div class="carousel-content">
<div class="supporter-item"><h4>J. Doe</h4><p>€500</p></div>
<div class="supporter-item"><h4>A. Smith</h4><p>€250</p></div>
<div class="supporter-item"><h4>M. Garcia</h4><p>€150</p></div>
<div class="supporter-item"><h4>J. Doe</h4><p>€500</p></div>
<div class="supporter-item"><h4>A. Smith</h4><p>€250</p></div>
<div class="supporter-item"><h4>M. Garcia</h4><p>€150</p></div>
<div class="supporter-item"><h4>L. Miller</h4><p>€100</p></div>
<div class="supporter-item"><h4>P. Jones</h4><p>€80</p></div>
<div class="supporter-item"><h4>T. Kim</h4><p>€60</p></div>
<div class="supporter-item"><h4>J. Doe</h4><p>€500</p></div>
<div class="supporter-item"><h4>A. Smith</h4><p>€250</p></div>
<div class="supporter-item"><h4>M. Garcia</h4><p>€150</p></div>
<div class="supporter-item"><h4>L. Miller</h4><p>€100</p></div>
<div class="supporter-item"><h4>P. Jones</h4><p>€80</p></div>
<div class="supporter-item"><h4>T. Kim</h4><p>€60</p></div>
</div> </div>
</div> </div>
</div> </div>
</section>
{% endblock %} {% endblock %}

View File

@ -1,26 +0,0 @@
{% extends 'base.html' %}
{% block content %}
{% load crispy_forms_tags %}
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<br><br>
<div class="container mt-5">
<form method="POST">
{% csrf_token %}
<div class="card mb-4">
<div class="card-header">
Login
</div>
<div class="card-body">
{{ form | crispy }}
<br>
<button class="btn btn-light" type="submit"> Login </button>
</div>
<div class="card-footer">
<small class="text-muted">
Don't Have An Account? <a class="link" href="{% url 'Register' %}">Register</a>
</small>
</div>
</div>
</form>
</div>
{% endblock %}

View File

@ -1,28 +0,0 @@
{% extends 'base.html' %}
{% block content %}
{% load crispy_forms_tags %}
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<br><br>
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{message.tags}}">{{ message }}</div>
{% endfor %}
{% endif %}
<div class="container mt-5">
<div class="card mb-4">
<div class="card-header h5">
You have been Logged Out
</div>
<div class="card-body">
Hope you enjoyed Today!
</div>
<div class="card-footer">
<small class="text-muted">
Go to <a href="{% url 'Home' %}" class="link">Home</a> or <a href="{% url 'Login' %}" class="link" type="submit"> Log in </a> Again?
</small>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,58 +0,0 @@
{% extends 'base.html' %}
{% block content %}
{% load markdown_extras %}
<!-- Main Content -->
<div class="container mt-5">
<div class="row">
<!-- Mainbar -->
<div class="col-lg-8">
<div class="card-body">
<div class="card">
<div class="card-header">
Latest Posts
</div>
<div class="card-body">
{% for post in posts %}
{% if post.author == profileUser %}
<!-- Posts will be dynamically added here -->
<div class="card mb-3 userPost">
<div class="card-body">
<h5 class="card-title">{{ post.author.username }} <a href="#" class="link text-secondary lead" style="text-decoration: none;">@{{ post.author.id }}</a></h5>
<p class="card-text">{{ post.content | markdown | safe }}</p>
</div>
</div>
<!-- End of Posts -->
{% endif %}
{% endfor %}
</div>
</div>
</div>
</div>
<!-- Sidebar -->
<div class="col-lg-4">
<div class="card mb-4">
<div class="row" style="padding-bottom: 20px;">
<div class="col-md-4 text-center">
<img src="https://cdn.pixabay.com/photo/2017/06/13/12/54/profile-2398783_1280.png" alt="Profile Image" class="profile-image">
</div>
<div class="col-md-8">
<h2 class="mt-3" style="margin-bottom: 0px;">{{ profileUser.username }}</h2>
<p class="text-secondary lead" style="margin-bottom: 0px;">@{{ profileUser.id }}</p>
<p>Date Joined: Jan 1, 2022</p>
{% if profileUser == user %}
<a href="#" class="link">Edit Profile</a>
{% endif %}
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,27 +0,0 @@
{% extends 'base.html' %}
{% block content %}
{% load crispy_forms_tags %}
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<br>
<br>
<div class="container mt-5">
<form method="POST">
{% csrf_token %}
<div class="card mb-4">
<div class="card-header">
Create an Account
</div>
<div class="card-body">
{{ form | crispy }}
<br>
<button class="btn btn-light sm" type="submit"> Register </button>
</div>
<div class="card-footer">
<small class="text-muted">
Already Have An Account? <a class="link" href="{% url 'Login' %}">Sign In</a>
</small>
</div>
</div>
</form>
</div>
{% endblock %}

View File

View File

@ -1,5 +0,0 @@
from django.contrib import admin
from .models import *
admin.site.register(UserData)

View File

@ -1,8 +0,0 @@
from django.apps import AppConfig
class UsersConfig(AppConfig):
name = 'users'
def ready(self):
import users.signals

View File

@ -1,21 +0,0 @@
from django import forms
from django.contrib.auth.models import User
from django.contrib.auth.forms import UserCreationForm
from .models import *
class UserRegisterForm(UserCreationForm):
email = forms.EmailField()
class Meta:
model = User
fields = ['username', 'email', 'password1', 'password2']
class UserUpdateForm(forms.ModelForm):
email = forms.EmailField()
class Meta:
model = User
fields = ['username', 'email']

View File

@ -1,28 +0,0 @@
# Generated by Django 5.0.2 on 2024-07-06 16:24
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Profile',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100)),
('about', models.TextField(default='Hi, I am new to TechBlog')),
('gender', models.TextField(default='None')),
('dob', models.DateField(default='1999-01-01')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 5.2.9 on 2026-02-19 10:09
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='profile',
name='id',
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
),
]

View File

@ -1,31 +0,0 @@
# Generated by Django 5.2.9 on 2026-02-28 03:45
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0002_alter_profile_id'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='UserData',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=40)),
('xp', models.IntegerField()),
('money', models.IntegerField()),
('equipment', models.CharField(default='0;', max_length=1024)),
('inventory', models.CharField(default='0;', max_length=1024)),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.DeleteModel(
name='Profile',
),
]

View File

@ -1,24 +0,0 @@
# Generated by Django 5.2.9 on 2026-03-05 12:51
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0003_userdata_delete_profile'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='UserAction',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('action', models.CharField(max_length=200)),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@ -1,30 +0,0 @@
from django.db import models
from django.contrib.auth.models import User
# Create your models here.
class UserData(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
name = models.CharField(max_length=40)
xp = models.IntegerField()
money = models.IntegerField()
equipment = models.CharField(max_length=1024, default='0;')
inventory = models.CharField(max_length=1024, default='0;')
def __str__(self):
return f"{self.user.username}'s data"
# So this is my beautiful brainchild to keep user data in about 1KB per user, I'm not too sure but still
# it's worth the try, so the main payload is the inventory along the soldiers and their weapons and
# vehicles along with the mods for everything which I have separated by `;`, `.` and `,` for soldier,
# equipment class and each equipment. I'll try a bit of bit-hacking to pack info of as much as 20 bytes
# in 2 for the mods. ammo|--|sights|---|internals|---|, trigger|--|barrel|--|skins|----|, number|--------|
class UserAction(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
action = models.CharField(max_length=200)
def __str__(self):
return f"{self.user.username}'s action"

View File

@ -1,13 +0,0 @@
from rest_framework import serializers
from .models import *
class UserDataSerializer(serializers.ModelSerializer):
class Meta:
model = UserData
fields = ['user', 'name', 'xp', 'money', 'equipment', 'inventory']
class UserActionSerializer(serializers.ModelSerializer):
class Meta:
model = UserAction
fields = ['user', 'action']

View File

@ -1,3 +0,0 @@
from django.db.models.signals import post_save
from django.contrib.auth.models import User
from django.dispatch import receiver

View File

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

View File

@ -1,11 +0,0 @@
from django.urls import path
from django.contrib.auth import views as login_view
from . import views
urlpatterns = [
# path('profile/', views.profile, name='Profile'),
# path('profile/<int:pk>', views.profile, name='NamedProfile'),
path('login/', login_view.LoginView.as_view(template_name='users/login.html'), name='Login'),
path('logout/', login_view.LogoutView.as_view(template_name='users/logout.html'), name='Logout'),
path('register/', views.register, name='Register')
]

View File

@ -1,75 +0,0 @@
from django.shortcuts import render, redirect
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from .forms import UserRegisterForm
from blog.models import *
from .models import *
from django.views.generic import *
# Create your views here.
# def users(request):
# return render(request, 'users/users.html', {'title': 'Users'})
# def getFromArr(arr, indices, *args, **kwargs):
# x = []
# for i in indices:
# x.append(arr[i])
# return x
# @login_required
# def profile(request, *args, **kwargs):
# try:
# user = User._default_manager.all()[kwargs['pk'] - 1]
# except:
# user = request.user
# print(user.id)
# allow_empty = True
# queryset = Post._default_manager.all()
# context_object_name = 'posts'
# ordering = ['-date_posted']
# if ordering:
# if isinstance(ordering, str):
# ordering = (ordering,)
# queryset = queryset.order_by(*ordering)
# return render(request, 'users/profile.html', {'title': 'Profile', 'profileUser': user, context_object_name: queryset})
# def people(request):
# return render(request, 'users/people.html', {'title': 'People', 'users': User._default_manager.all()})
users = {
'user': User.objects.all()
}
class UserListView(ListView):
model = User
template_name = 'users/people.html'
context_object_name = 'users'
def explore(request):
return render(request, 'users/explore.html', {'title': 'Explore'})
def login(request):
return render(request, 'users/login.html', {'title': 'Login'})
def register(request):
if request.method == 'POST':
form = UserRegisterForm(request.POST)
if form.is_valid():
form.save()
username = form.cleaned_data.get('username')
messages.success(request, f'{username}! Your account has been created.')
return redirect('Login')
else:
form = UserRegisterForm()
return render(request, 'users/register.html', {'title': 'Register', 'form': form})