Merge pull request #12 from kristoferssolo/development

Release v0.1.0
This commit is contained in:
Kristofers Solo 2023-06-29 19:54:38 +00:00 committed by GitHub
commit 27721e277d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 818 additions and 360 deletions

View File

@ -1,3 +0,0 @@
# from django.contrib import admin
# Register your models here.

View File

@ -1,3 +0,0 @@
# from django.db import models
# Create your models here.

View File

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

View File

@ -1,3 +0,0 @@
# from django.shortcuts import render
# Create your views here.

View File

@ -2,5 +2,5 @@ Django==4.2.2
Pillow==9.5.0
fontawesomefree==6.4.0
mysqlclient==2.1.1
django-filter==23.2
django-tailwind==3.6.0
django-search-views==0.3.7

View File

@ -1,34 +0,0 @@
[metadata]
name = FOSSDB-Web
description = Free and Open-Source Software Database Website
author = Kristofers Solo
license = GPL3
license_files = LICENSE
platforms = unix, linux, osx, cygwin, win32
classifiers =
Programming Language :: Python :: 3.10
[options]
packages = FOSSDB_web
install_requires =
Django>=4.1
fontawesomefree>=6.2.1
mysqlclient>=2.1.1
python_requires = >=3.10
package_dir = =.
zip_safe = no
[options.extras_require]
testing =
flake8>=6.0.0
mypy>=0.991
pytest-cov>=4.0.0
pytest>=7.2.0
tox>=3.27.1
[options.package_data]
detector = py.typed
[flake8]
max-line-length = 160

View File

@ -38,10 +38,9 @@ INSTALLED_APPS = [
"main",
"account",
"fossdb",
"django_filters",
"search_views",
"tailwind",
"tokyonight_night",
"crispy_forms",
"fontawesomefree",
"django.contrib.admin",
"django.contrib.auth",
@ -51,6 +50,13 @@ INSTALLED_APPS = [
"django.contrib.staticfiles",
]
TAILWIND_APP_NAME = "tokyonight_night"
INTERNAL_IPS = [
"127.0.0.1",
]
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
@ -157,12 +163,6 @@ LOGOUT_REDIRECT_URL = "/"
LOGIN_URL = "/login/"
TAILWIND_APP_NAME = "tokyonight_night"
INTERNAL_IPS = [
"127.0.0.1",
]
# HTTPS settings
# SESSION_COOKIE_SECURE = True
# CSRF_COOKIE_SECURE = True

View File

@ -19,10 +19,9 @@ from django.urls import include, path
urlpatterns = [
path("admin/", admin.site.urls),
path("", include("fossdb.urls")),
path("", include("main.urls")),
path("", include("account.urls")),
path("", include("django.contrib.auth.urls")),
path("auth/", include("account.urls")),
path("<str:owner>/<str:project_name>/", include("fossdb.urls")),
]
if settings.DEBUG:
from django.conf.urls.static import static

View File

@ -1,12 +1,31 @@
from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.forms import AuthenticationForm, UserChangeForm as BaseUserChangeForm, UserCreationForm
from .models import User
from .models import Profile, User
class LoginForm(AuthenticationForm):
def __init__(self, *args, **kwargs):
super(LoginForm, self).__init__(*args, **kwargs)
self.fields["username"].widget = forms.TextInput(
attrs={
"placeholder": "Username",
"class": "verify form-field submit-form",
}
)
self.fields["username"].label = ""
self.fields["password"].widget = forms.PasswordInput(
attrs={
"placeholder": "Password",
"class": "verify form-field submit-form",
}
)
self.fields["password"].label = ""
class SignUpForm(UserCreationForm):
email = forms.EmailField(required=False, help_text="Optional.")
email = forms.EmailField(required=False)
class Meta:
model = User
@ -16,3 +35,99 @@ class SignUpForm(UserCreationForm):
"password1",
"password2",
)
widgets = {
"username": forms.TextInput(
attrs={
"placeholder": "Username",
"class": "verify form-field submit-form",
}
),
"email": forms.EmailInput(
attrs={
"placeholder": "Email (optional)",
"class": "verify form-field submit-form",
}
),
"password1": forms.PasswordInput(
attrs={
"placeholder": "Password",
"class": "verify form-field submit-form",
}
),
"password2": forms.PasswordInput(
attrs={
"placeholder": "Confirm password",
"class": "verify form-field submit-form",
}
),
}
labels = {
"username": "",
"email": "",
"password1": "",
"password2": "",
}
class UserChangeForm(BaseUserChangeForm):
class Meta(BaseUserChangeForm.Meta):
model = User
fields = (
"username",
"email",
"first_name",
"last_name",
)
widgets = {
"username": forms.TextInput(
attrs={
"placeholder": "Username",
"class": "form-field submit-form",
}
),
"email": forms.EmailInput(
attrs={
"placeholder": "Email",
"class": "form-field submit-form",
}
),
"first_name": forms.TextInput(
attrs={
"placeholder": "First Name",
"class": "form-field submit-form",
}
),
"last_name": forms.TextInput(
attrs={
"placeholder": "Last Name",
"class": "form-field submit-form",
}
),
}
labels = {
"username": "",
"email": "",
"first_name": "",
"last_name": "",
}
help_text = {
"username": None,
"email": None,
"first_name": None,
"last_name": None,
}
class ProfileForm(forms.ModelForm):
class Meta:
model = Profile
fields = ("picture",)
labels = {
"picture": "",
}
help_text = {
"picture": None,
}

View File

@ -8,25 +8,13 @@
<form method="post"
class="flex flex-col items-center justify-center space-y-4 my-auto">
{% csrf_token %}
<input type="text"
placeholder="Username"
name="username"
value="{{ form.username.value|default:'' }}"
class="verify mt-1 block rounded-md border-2 border-slategray-200 hover:border-lightsteelblue-100 bg-gray-300 placeholder-lightsteelblue-100 focus:border-cadetblue-300" />
{% if form.username.errors %}<p class="text-indianred-100 text-xs italic">{{ form.username.errors }}</p>{% endif %}
<input type="password"
placeholder="Password"
name="password"
value="{{ form.username.value|default:'' }}"
class="verify mt-1 block rounded-md border-2 border-slategray-200 hover:border-lightsteelblue-100 bg-gray-300 placeholder-lightsteelblue-100 focus:border-cadetblue-300" />
{% if form.password.errors %}<p class="text-indianred-100 text-xs italic">{{ form.password.errors }}</p>{% endif %}
{{ form }}
<button type="submit"
id="submit-button"
class="px-12 py-2 border border-transparent rounded-md text-lightsteelblue-100 bg-slategray-200 opacity-50 transform ease-in-out duration-500 cursor-default">
Login
</button>
class="button submit-button-disabled text-lightsteelblue-100 bg-slategray-200">Login</button>
<p>
Don't have an account? Create one <a class="underline text-skyblue-300" href="{% url 'signup' %}">Here</a>!
Don't have an account? Create one <a class="underline text-skyblue-300 hover:text-cadetblue-300 transform duration-300 ease-linear"
href="{% url 'signup' %}">Here</a>!
</p>
</form>
{% endblock %}

View File

@ -0,0 +1,16 @@
{% extends "base.html" %}
{% load static %}
{% block title %}{{ title }}{% endblock %}
{% block meta %}{% endblock %}
{% block content %}
<div class="py-8 px-32">
<form method="POST"
enctype="multipart/form-data"
class="flex flex-col items-center justify-center space-y-4 my-auto">
<h2 class="text-3xl font-abel">Change password</h2>
{% csrf_token %}
{{ form }}
<button type="submit" class="button bg-skyblue-300 text-gray-500">Change</button>
</form>
</div>
{% endblock %}

View File

@ -3,8 +3,28 @@
{% block title %}{{ title }}{% endblock %}
{% block meta %}{% endblock %}
{% block content %}
<h1>{{ user.username }}</h1>
<img src="{{ user.profile_picture.url }}"
alt="{{ user.username }}s profile picture" />
<p>{{ user.email }}</p>
<div class="py-8 px-32">
<div class="flex justify-end gap-4">
<a href="{% url 'logout' %}">
<i class="fa-solid fa-right-from-bracket fa-2xl"></i>
</a>
<a href="{% url 'settings' %}">
<i class="fa-solid fa-gear fa-2xl"></i>
</a>
</div>
<h1 class="font-abel text-4xl mb-8">My Projects</h1>
<div class="grid grid-cols-1 gap-4">
{% for project in projects %}
<div class="border border-steelblue-100 p-8 flex flex-row gap-4 justify-between">
<a href="{{ project.get_absolute_url }}"
class="hover:text-steelblue-100 transition duration-300 ease-linear">
<h2 class="font-abel text-3xl">{{ project.name }}</h2>
</a>
<p class="max-w-2xl text-justify">{{ project.description|slice:500 }}</p>
</div>
{% empty %}
<p class="text-center font-abel text-xl">No projects yet.</p>
{% endfor %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,24 @@
{% extends "base.html" %}
{% load static %}
{% block title %}{{ title }}{% endblock %}
{% block meta %}{% endblock %}
{% block content %}
<div class="py-8 px-32">
<div class="flex justify-end">
<a href="{% url 'change_password' %}">
<i class="fa-solid fa-shield fa-2xl"></i>
</a>
</div>
<form method="POST"
enctype="multipart/form-data"
class="flex flex-col items-center justify-center space-y-4 my-auto">
<h2 class="text-3xl font-abel">Edit profile</h2>
{% csrf_token %}
{{ user_form.username }}
{{ user_form.email }}
{{ user_form.first_name }}
{{ user_form.last_name }}
<button type="submit" class="button bg-skyblue-300 text-gray-500">Save</button>
</form>
</div>
{% endblock %}

View File

@ -13,33 +13,31 @@
placeholder="Username"
name="username"
value="{{ form.username.value|default:'' }}"
class="verify mt-1 block rounded-md border-2 border-slategray-200 hover:border-lightsteelblue-100 bg-gray-300 placeholder-lightsteelblue-100 focus:border-cadetblue-300" />
class="verify form-field submit-form" />
{% if form.username.errors %}<p class="text-indianred-100 text-xs italic">{{ form.username.errors }}</p>{% endif %}
<input type="email"
placeholder="Email (optional)"
name="email"
value="{{ form.email.value|default:'' }}"
class="mt-1 block rounded-md border-2 border-slategray-200 hover:border-lightsteelblue-100 bg-gray-300 placeholder-lightsteelblue-100 focus:border-cadetblue-300" />
class="form-field submit-form" />
{% if form.email.errors %}<p class="text-indianred-100 text-xs italic">{{ form.email.errors }}</p>{% endif %}
<input type="password"
placeholder="Password"
name="password1"
value="{{ form.password1.value|default:'' }}"
class="verify mt-1 block rounded-md border-2 border-slategray-200 hover:border-lightsteelblue-100 bg-gray-300 placeholder-lightsteelblue-100 focus:border-cadetblue-300" />
class="verify form-field submit-form" />
{% if form.password1.errors %}<p class="text-indianred-100 text-xs italic">{{ form.password1.errors }}</p>{% endif %}
<input type="password"
placeholder="Confirm password"
name="password2"
value="{{ form.password2.value|default:'' }}"
class="verify mt-1 block rounded-md border-2 border-slategray-200 hover:border-lightsteelblue-100 bg-gray-300 placeholder-lightsteelblue-100 focus:border-cadetblue-300" />
class="verify form-field submit-form" />
{% if form.password2.errors %}<p class="text-indianred-100 text-xs italic">{{ form.password2.errors }}</p>{% endif %}
<button type="submit"
id="submit-button"
class="px-12 py-2 border border-transparent rounded-md text-lightsteelblue-100 bg-slategray-200 opacity-50 transform ease-in-out duration-500 cursor-default">
Sign Up
</button>
class="button submit-button-disabled text-lightsteelblue-100 bg-slategray-200" />Sign Up</button>
<p>
Have an account? Login <a class="underline text-skyblue-300" href="{% url 'login' %}">Here</a>!
</p>
</form>
</form>
{% endblock %}

View File

@ -5,5 +5,7 @@ from . import views
urlpatterns = [
path("signup/", views.signup_view, name="signup"),
path("login/", views.login_view, name="login"),
path("<str:username>/", views.profile, name="profile"),
path("logout/", views.LogoutView.as_view(), name="logout"),
path("settings/", views.ProfileUpdateView.as_view(), name="settings"),
path("settings/security/", views.PasswordChangeView.as_view(), name="change_password"),
]

View File

@ -1,19 +1,82 @@
from django.contrib.auth import authenticate, login
from django.contrib.auth.forms import AuthenticationForm
from django.shortcuts import get_object_or_404, redirect, render
from django.contrib import messages
from django.contrib.auth import authenticate, login, logout, update_session_auth_hash
from django.contrib.auth.forms import PasswordChangeForm
from django.contrib.auth.mixins import LoginRequiredMixin
from django.shortcuts import redirect, render
from django.views.generic import ListView, TemplateView, View
from .forms import SignUpForm
from .models import User
from fossdb.models import Project
from .forms import LoginForm, SignUpForm, UserChangeForm
def profile(request, username):
user = get_object_or_404(User, username=username)
class ProfileUpdateView(LoginRequiredMixin, TemplateView):
template_name = "setting.html"
login_url = "/login/"
redirect_field_name = "redirect_to"
def get(self, request):
user_form = UserChangeForm(instance=request.user)
context = {
"title": "Your profile",
"user_form": user_form,
}
return self.render_to_response(context)
def post(self, request):
user_form = UserChangeForm(request.POST, instance=request.user)
if user_form.is_valid():
user_form.save()
messages.add_message(request, messages.SUCCESS, "Your profile was successfully updated!")
context = {
"title": user.username + ("" if not user.full_name else f" ({user.full_name})"),
"user": user,
"title": "Your profile",
"user_form": user_form,
}
return render(request, "profile.html", context)
return self.render_to_response(context)
class PasswordChangeView(LoginRequiredMixin, TemplateView):
template_name = "password.html"
def get(self, request):
form = PasswordChangeForm(user=request.user)
context = {
"title": "Change password",
"form": form,
}
return self.render_to_response(context)
def post(self, request):
form = PasswordChangeForm(data=request.POST, user=request.user)
if form.is_valid():
form.save()
update_session_auth_hash(request, form.user)
messages.add_message(request, messages.SUCCESS, "Your password was successfully updated!")
context = {
"title": "Change password",
"form": form,
}
return self.render_to_response(context)
class ProfileProjectListView(LoginRequiredMixin, ListView):
model = Project
template_name = "profile.html"
context_object_name = "projects"
login_url = "/login/"
redirect_field_name = "redirect_to"
def get_queryset(self):
return Project.objects.filter(owner=self.request.user)
def get_context_data(self, *args, **kwargs):
data = super().get_context_data(**kwargs)
data["title"] = self.request.user.username + ("" if not self.request.user.full_name else f" ({self.request.user.full_name})")
return data
def signup_view(request):
@ -34,7 +97,7 @@ def signup_view(request):
def login_view(request):
form = AuthenticationForm(data=request.POST or None)
form = LoginForm(data=request.POST or None)
if request.method == "POST":
if form.is_valid():
user = form.get_user()
@ -46,3 +109,9 @@ def login_view(request):
"form": form,
}
return render(request, "login.html", context)
class LogoutView(View):
def get(self, request):
logout(request)
return redirect("login")

View File

@ -1,28 +0,0 @@
import django_filters
from django.contrib.auth import get_user_model
from .models import Project
User = get_user_model()
class UserFilter(django_filters.FilterSet):
username = django_filters.CharFilter(lookup_expr="icontains")
class Meta:
model = User
fields = ("username",)
class ProjectFilter(django_filters.FilterSet):
owner = UserFilter()
name = django_filters.CharFilter(lookup_expr="icontains")
description = django_filters.CharFilter(lookup_expr="icontains")
class Meta:
model = Project
fields = (
"owner",
"name",
"description",
)

View File

@ -1,19 +1,33 @@
from django import forms
from django.forms import inlineformset_factory
from .models import HostingPlatform, ProgrammingLanguage, Project, ProjectHostingPlatform, ProjectProgrammingLanguage
from .models import HostingPlatform, License, OperatingSystemVersion, ProgrammingLanguage, Project, ProjectHostingPlatform, ProjectProgrammingLanguage, Tag
class HostingPlatformForm(forms.ModelForm):
class Meta:
model = ProjectHostingPlatform
fields = (
"url",
"hosting_platform",
"url",
)
widgets = {
"hosting_platform": forms.Select(
choices=HostingPlatform.objects.all(),
)
attrs={
"class": "form-field submit-form",
},
),
"url": forms.URLInput(
attrs={
"placeholder": "url",
"class": "form-field submit-form font-roboto",
},
),
}
labels = {
"hosting_platform": "",
"url": "",
}
@ -27,14 +41,32 @@ class ProgrammingLanguageForm(forms.ModelForm):
widgets = {
"programming_language": forms.Select(
choices=ProgrammingLanguage.objects.all(),
attrs={
"class": "form-field submit-form",
},
),
"percentage": forms.NumberInput(
attrs={
"placeholder": "Percentage",
"class": "form-field submit-form",
"min": "0",
"max": "100",
}
},
),
}
labels = {
"programming_language": "",
"percentage": "",
}
ProgrammingLanguageInlineFormSet = inlineformset_factory(
Project,
ProjectProgrammingLanguage,
form=ProgrammingLanguageForm,
extra=0,
can_delete=True,
)
class ProjectForm(forms.ModelForm):
@ -51,17 +83,39 @@ class ProjectForm(forms.ModelForm):
widgets = {
"name": forms.TextInput(
attrs={
"class": "form-control",
"placeholder": "Project name",
}
"class": "form-field submit-form font-roboto",
},
),
"description": forms.Textarea(
attrs={
"class": "form-control",
"placeholder": "Description",
}
"class": "form-field submit-form font-roboto",
},
),
"license": forms.CheckboxSelectMultiple(
choices=License.objects.all(),
attrs={
"class": "checkbox-form",
},
),
"operating_system": forms.CheckboxSelectMultiple(
choices=OperatingSystemVersion.objects.all(),
attrs={
"class": "checkbox-form",
},
),
"tag": forms.CheckboxSelectMultiple(
choices=Tag.objects.all(),
attrs={
"class": "checkbox-form",
},
),
"license": forms.CheckboxSelectMultiple(),
"tag": forms.CheckboxSelectMultiple(),
"operating_system": forms.CheckboxSelectMultiple(),
}
labels = {
"name": "",
"description": "",
"license": "",
"tag": "",
"operating_system": "",
}

View File

@ -0,0 +1,18 @@
# Generated by Django 4.2.2 on 2023-06-29 14:29
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('fossdb', '0005_operatingsystem_is_linux'),
]
operations = [
migrations.AlterField(
model_name='projectprogramminglanguage',
name='percentage',
field=models.PositiveIntegerField(blank=True, default=0),
),
]

View File

@ -0,0 +1,43 @@
# Generated by Django 4.2.2 on 2023-06-29 15:06
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('fossdb', '0006_alter_projectprogramminglanguage_percentage'),
]
operations = [
migrations.AlterField(
model_name='license',
name='full_name',
field=models.CharField(db_index=True, max_length=100, unique=True),
),
migrations.AlterField(
model_name='license',
name='short_name',
field=models.CharField(db_index=True, max_length=50, unique=True),
),
migrations.AlterField(
model_name='operatingsystem',
name='name',
field=models.CharField(db_index=True, max_length=100, unique=True),
),
migrations.AlterField(
model_name='programminglanguage',
name='name',
field=models.CharField(db_index=True, max_length=100, unique=True),
),
migrations.AlterField(
model_name='project',
name='description',
field=models.TextField(blank=True, db_index=True, default=''),
),
migrations.AlterField(
model_name='project',
name='name',
field=models.CharField(db_index=True, max_length=255),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 4.2.2 on 2023-06-29 15:07
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('fossdb', '0007_alter_license_full_name_alter_license_short_name_and_more'),
]
operations = [
migrations.AlterField(
model_name='project',
name='description',
field=models.TextField(blank=True, default=''),
),
]

View File

@ -0,0 +1,16 @@
# Generated by Django 4.2.2 on 2023-06-29 16:51
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('fossdb', '0008_alter_project_description'),
]
operations = [
migrations.DeleteModel(
name='Star',
),
]

View File

@ -6,8 +6,8 @@ from django.db import models
class License(models.Model):
short_name = models.CharField(max_length=50, unique=True)
full_name = models.CharField(max_length=100, unique=True)
short_name = models.CharField(max_length=50, unique=True, db_index=True)
full_name = models.CharField(max_length=100, unique=True, db_index=True)
url = models.URLField(blank=True, default="")
text = models.TextField(blank=True, default="")
@ -16,7 +16,7 @@ class License(models.Model):
class OperatingSystem(models.Model):
name = models.CharField(max_length=100, unique=True)
name = models.CharField(max_length=100, unique=True, db_index=True)
description = models.TextField(blank=True, default="")
is_linux = models.BooleanField(blank=True, default=False)
@ -55,7 +55,7 @@ class Tag(models.Model):
class ProgrammingLanguage(models.Model):
name = models.CharField(max_length=100, unique=True)
name = models.CharField(max_length=100, unique=True, db_index=True)
def __str__(self):
return self.name
@ -64,7 +64,7 @@ class ProgrammingLanguage(models.Model):
class Project(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, verbose_name="ID")
owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
name = models.CharField(max_length=255)
name = models.CharField(max_length=255, db_index=True)
description = models.TextField(blank=True, default="")
license = models.ManyToManyField(License, blank=True)
tag = models.ManyToManyField(Tag, blank=True)
@ -72,10 +72,6 @@ class Project(models.Model):
programming_language = models.ManyToManyField(ProgrammingLanguage, through="ProjectProgrammingLanguage", blank=True)
date_created = models.DateTimeField(auto_now_add=True)
@property
def star_amount(self):
return self.star.count()
@property
def runs_on_macos(self):
return self.operating_system.filter(operating_system__name="macOS").exists()
@ -117,7 +113,7 @@ class Project(models.Model):
class ProjectProgrammingLanguage(models.Model):
project = models.ForeignKey(Project, on_delete=models.CASCADE)
programming_language = models.ForeignKey(ProgrammingLanguage, on_delete=models.CASCADE)
percentage = models.PositiveIntegerField(blank=True, null=True)
percentage = models.PositiveIntegerField(blank=True, default=0)
def __str__(self):
return f"{self.project.owner}/{self.project.name} | {self.programming_language} | {self.percentage}%"
@ -137,8 +133,3 @@ class ProjectHostingPlatform(models.Model):
def __str__(self):
return self.url
class Star(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
project = models.ForeignKey(Project, on_delete=models.CASCADE)

View File

@ -3,32 +3,62 @@
{% block title %}{{ title }}{% endblock %}
{% block meta %}{% endblock %}
{% block content %}
<form method="post" id="project-form">
<form class="flex flex-col items-center justify-center space-y-4 my-auto font-condensed"
method="post"
id="project-form">
{% csrf_token %}
{{ form.as_p }}
{{ hosting_platform.management_form }}
{{ hosting_platform.as_table }}
<div id="language-formset">
{{ programming_language.management_form }}
{% for form in programming_language %}<div class="language-form">{{ form.as_table }}</div>{% endfor %}
{{ form.name }}
{{ form.description }}
<div class="flex gap-4 justify-center">
<div class="">
<p class="text-xl font-abel text-center">Licenses</p>
{{ form.license }}
</div>
<!-- This button will trigger the JS to append another language form -->
<button type="button" id="add-more">+</button>
<!-- Render the empty form, which you'll use as a template for new entries -->
<!-- Wrap it in a container so you can reference it by id and hide it -->
<div id="empty-form" style="display:none;">{{ empty_form.as_table }}</div>
<button type="submit">Submit</button>
<div>
<p class="text-xl font-abel text-center">Tags</p>
{{ form.tag }}
</div>
<div>
<p class="text-xl font-abel text-center">Operating systems</p>
{{ form.operating_system }}
</div>
</div>
{{ hosting_platform.management_form }}
<div class="flex gap-4 items-center">
<p class="text-xl font-abel">Hosting platform:</p>
{{ hosting_platform }}
</div>
{{ programming_languages.management_form }}
<!-- Languages container -->
<div class="" id="languages-container">
{% for language in programming_languages %}<div class="flex gap-4 items-center">{{ language }}</div>{% endfor %}
</div>
<!-- Hidden empty form used as a template -->
<div id="empty-form" style="display:none">
<div class="flex gap-4 items-center">{{ programming_languages.empty_form }}</div>
</div>
<div class="flex justify-between">
<div class="p-2 text-mediumaquamarine cursor-pointer hover:opacity-50 transition duration-100 ease-linear"
id="add-language">
<i class="fa-solid fa-plus fa-2xl"></i>
</div>
</div>
<button class="button submit-button-enabled bg-mediumaquamarine text-gray-500"
ype="submit">Save</button>
</form>
<script>
document.querySelector("#add-more").addEventListener("click", function() {
var formIndex = document.querySelector("#id_language-TOTAL_FORMS").value;
var emptyFormDiv = document.querySelector("#empty-form");
var newFormHTML = emptyFormDiv.innerHTML.replace(/__prefix__/g, formIndex);
var newFormDiv = document.createElement("div");
newFormDiv.className = "language-form";
newFormDiv.innerHTML = newFormHTML;
document.querySelector("#language-formset").append(newFormDiv);
document.querySelector("#id_language-TOTAL_FORMS").value = parseInt(formIndex) + 1;
});
<script type="text/javascript">
window.addEventListener("DOMContentLoaded", () => {
document.getElementById("add-language").addEventListener("click", () => {
var formIndex = document.getElementById("id_projectprogramminglanguage_set-TOTAL_FORMS");
var emptyForm = document.getElementById("empty-form").innerHTML;
emptyForm = emptyForm.replace(/__prefix__/g, formIndex.value);
document.getElementById("languages-container")
.insertAdjacentHTML("beforeend", emptyForm);
formIndex.value = parseInt(formIndex.value) + 1;
})
})
</script>
{% endblock %}

View File

@ -1,6 +1,6 @@
{% extends "base.html" %}
{% load static %}
{% block title %}Delete {{ project }}{% endblock %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<form method="post"
id="delete-form"
@ -9,9 +9,9 @@
{% csrf_token %}
<input type="text"
id="confirm-input"
class="mt-1 block rounded-md border-2 border-slategray-200 hover:border-lightsteelblue-100 bg-gray-300 placeholder-lightsteelblue-100 focus:border-cadetblue-300 text-center" />
class="text-center form-field border-slategray-200 hover:border-lightcoral bg-gray-300 focus:border-indianred-100 transition ease-linear" />
<button id="submit-button"
class="px-12 py-2 border border-transparent rounded-md text-lightsteelblue-100 font-bold uppercase bg-slategray-200 opacity-50 transform ease-in-out duration-500 cursor-default"
class="button submit-button-disabled text-lightsteelblue-100 bg-slategray-200 uppercase font-bold"
type="submit">Delete</button>
</form>
<script type="text/javascript">
@ -23,30 +23,26 @@
USER_INPUT.addEventListener("input", () => {
if (confirm_string == USER_INPUT.value) {
SUBMIT_BUTTON.classList.remove(
"bg-slategray-200",
"submit-button-disabled",
"text-lightsteelblue-100",
"opacity-50",
"cursor-default"
"bg-slategray-200"
)
SUBMIT_BUTTON.classList.add(
"bg-indianred-100",
"submit-button-enabled",
"text-gray-500",
"opacity-100",
"hover:opacity-60"
"bg-indianred-100",
)
SUBMIT_BUTTON.disabled = false
} else {
SUBMIT_BUTTON.classList.remove(
"bg-indianred-100",
"submit-button-enabled",
"text-gray-500",
"opacity-100",
"hover:opacity-60"
"bg-indianred-100",
)
SUBMIT_BUTTON.classList.add(
"bg-slategray-200",
"submit-button-disabled",
"text-lightsteelblue-100",
"opacity-50",
"cursor-default"
"bg-slategray-200"
)
SUBMIT_BUTTON.disabled = true
}

View File

@ -13,45 +13,73 @@
</h2>
<!-- os platform icons -->
<div class="my-4 flex justify-center items-center gap-x-4">
<a href=""><i class="fa-brands fa-linux fa-lg hover:text-lightsteelblue-200 transform duration-300 ease-in-out
<!-- linux -->
<a href="/search/?q=linux">
<span title="Linux"
class="hover:text-lightsteelblue-200 transform duration-200 ease-linear
{% if project.runs_on_linux %}
text-lightsteelblue-100
{% else %}
text-slategray-200
{% endif %}"></i></a>
<a href=""><i class="fa-brands fa-windows fa-lg hover:text-lightsteelblue-200 transform duration-300 ease-in-out
{% endif %}">
<i class="fa-brands fa-linux fa-lg"></i>
</span>
</a>
<!-- windows -->
<a href="/search/?q=windows">
<span title="Windows"
class="hover:text-lightsteelblue-200 transform duration-200 ease-linear
{% if project.runs_on_windows %}
text-lightsteelblue-100
{% else %}
text-slategray-200
{% endif %}"></i></a>
<a href=""><i class="fa-brands fa-apple fa-lg hover:text-lightsteelblue-200 transform duration-300 ease-in-out
{% endif %}">
<i class="fa-brands fa-windows fa-lg"></i>
</span>
</a>
<!-- macos -->
<a href="/search/?q=macos">
<span title="macOS"
class="hover:text-lightsteelblue-200 transform duration-200 ease-linear
{% if project.runs_on_macos %}
text-lightsteelblue-100
{% else %}
text-slategray-200
{% endif %}"></i></a>
<a href=""><i id="ios"
class="fa-brands fa-app-store-ios fa-lg hover:text-lightsteelblue-200 transform duration-300 ease-in-out
{% endif %}">
<i class="fa-brands fa-apple fa-lg"></i>
</span>
</a>
<!-- ios -->
<a href="/search/?q=ios">
<span title="iOS"
class="fa-lg hover:text-lightsteelblue-200 transform duration-200 ease-linear
{% if project.runs_on_ios %}
text-lightsteelblue-100
{% else %}
text-slategray-200
{% endif %}"></i></a>
<a href=""><i id="android"
class="fa-brands fa-android fa-lg hover:text-lightsteelblue-200 transform duration-300 ease-in-out
{% endif %}">
<i class="fa-brands fa-app-store-ios"></i>
</span>
</a>
<!-- android -->
<a href="/search/?q=android">
<span title="Android"
class="hover:text-lightsteelblue-200 transform duration-200 ease-linear
{% if project.runs_on_android %}
text-lightsteelblue-100
{% else %}
text-slategray-200
{% endif %}"></i></a>
{% endif %}">
<i class="fa-brands fa-android fa-lg"></i>
</span>
</a>
</div>
<!-- tags -->
<div class="my-8 flex flex-wrap justify-center items-start gap-2">
{% for tag in project.tag.all|dictsort:"name" %}
<a href="">
<a href="/search/?q={{ tag.name }}">
<span title="{{ tag.description }}"
class="bg-opacity-0 border rounded-xl border-slategray-200 px-3 text-xs min-w-16 hover:bg-steelblue-400 hover:bg-opacity-60 transform duration-300 ease-in-out">
class="bg-opacity-0 border rounded-xl border-slategray-200 px-3 text-xs min-w-16 hover:bg-steelblue-400 hover:bg-opacity-60 transform duration-200 ease-linear">
{{ tag }}
</span>
</a>
@ -60,9 +88,9 @@
<!-- programming languages -->
<div class="my-8 flex flex-wrap justify-center items-start gap-2">
{% for language in project.projectprogramminglanguage_set.all|dictsortreversed:"percentage" %}
<a href="">
<a href="/search/?q={{ language.name }}">
<span title="{{ language.percentage }}%"
class="bg-opacity-0 border rounded-xl border-slategray-200 px-3 text-xs min-w-16 hover:bg-steelblue-400 hover:bg-opacity-60 transform duration-300 ease-in-out">
class="bg-opacity-0 border rounded-xl border-slategray-200 px-3 text-xs min-w-16 hover:bg-steelblue-400 hover:bg-opacity-60 transform duration-200 ease-linear">
{{ language.programming_language }}
</span>
</a>
@ -75,7 +103,7 @@
<div class="flex justify-between w-full mt-4 font-abel">
<!-- licenses -->
<div>
<button class="text-xl underline font-abel hover:text-lightsteelblue-200 transform duration-300 ease-in-out"
<button class="text-xl underline font-abel hover:text-lightsteelblue-200 transform duration-200 ease-linear"
id="menu-button"
aria-haspopup="true"
aria-expanded="true">Licenses</button>
@ -83,7 +111,7 @@
<!-- hosting platform -->
<div class="">
<p>
See project source code and read more <a class="underline text-skyblue-300 hover:text-cadetblue-300 transform duration-300 ease-in-out"
See project source code and read more <a class="underline text-skyblue-300 hover:text-cadetblue-300 transform duration-200 ease-linear"
href="{{ project.projecthostingplatform.url }}"
target="_blank">here</a>!
</p>
@ -100,7 +128,7 @@
{% for license in project.license.all|dictsort:"short_name" %}
<a href="{{ license.url }}">
<span title="{{ license.full_name }}"
class="bg-opacity-0 border rounded-xl border-slategray-200 px-3 text-xs min-w-16 hover:bg-steelblue-400 hover:bg-opacity-60">
class="bg-opacity-0 border rounded-xl border-slategray-200 px-3 text-xs min-w-16 hover:bg-steelblue-400 hover:bg-opacity-60 transform duration-200 ease-linear">
{{ license }}
</span>
</a>
@ -111,12 +139,12 @@
</div>
{% if user == project.owner %}
<div class="flex justify-between mt-8 mx-16">
<button class="px-12 py-2 border border-transparent rounded-md text-gray-500 bg-skyblue-300 hover:opacity-60 transform ease-in-out duration-500">
<a href="{% url 'project-update' project.owner project.name %}">Update</a>
</button>
<button class="px-12 py-2 border border-transparent rounded-md text-gray-500 bg-burlywood hover:opacity-60 transform ease-in-out duration-500">
<a href="{% url 'project-delete' project.owner project.name %}">Delete</a>
</button>
<a href="{% url 'project-update' project.owner project.name %}">
<button class="button hover:bg-opacity-60 bg-mediumpurple-100 text-gray-500">Update</button>
</a>
<a href="{% url 'project-delete' project.owner project.name %}">
<button class="button hover:bg-opacity-60 bg-lightcoral text-gray-500">Delete</button>
</a>
</div>
{% endif %}
</div>

View File

@ -3,13 +3,20 @@
{% block title %}{{ title }}{% endblock %}
{% block meta %}{% endblock %}
{% block content %}
<div class="py-8 px-16">
<h1 class="font-abel text-4xl mb-8">Projects</h1>
<div class="grid grid-cols-2 gap-4">
{% for project in projects %}
<div>
<a href="{{ project.get_absolute_url }}">{{ project }}</a>
<div class="border border-steelblue-100 p-8 flex flex-row gap-4 justify-between">
<a href="{{ project.get_absolute_url }}"
class="hover:text-steelblue-100 transition duration-300 ease-linear">
<h2 class="font-abel text-2xl">{{ project.name }}</h2>
</a>
<p class="max-w-xs text-justify">{{ project.description|slice:500 }}</p>
</div>
<div>{{ project.description }}</div>
<hr />
{% empty %}
<p>No projects found</p>
<p class="text-center font-abel text-xl">No projects yet.</p>
{% endfor %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,22 @@
{% extends "base.html" %}
{% load static %}
{% block title %}{{ title }}{% endblock %}
{% block meta %}{% endblock %}
{% block content %}
<div class="py-8 px-32">
<h1 class="font-abel text-4xl mb-8">Search Results</h1>
<div class="grid grid-cols-1 gap-4">
{% for project in projects %}
<div class="border border-steelblue-100 p-8 flex flex-row gap-4 justify-between">
<a href="{{ project.get_absolute_url }}"
class="hover:text-steelblue-100 transition duration-300 ease-linear">
<h2 class="font-abel text-3xl">{{ project.name }}</h2>
</a>
<p class="max-w-2xl text-justify">{{ project.description|slice:500 }}</p>
</div>
{% empty %}
<p class="text-center font-abel text-xl">No projects yet.</p>
{% endfor %}
</div>
</div>
{% endblock %}

View File

@ -3,9 +3,7 @@ from django.urls import path
from . import views
urlpatterns = [
path("explore/", views.ProjectListView.as_view(), name="explore"),
path("contribute/", views.ProjectCreateView.as_view(), name="contribute"),
path("<str:owner>/<str:project_name>/", views.ProjectDetailView.as_view(), name="project-detail"),
path("<str:owner>/<str:project_name>/edit/", views.ProjectUpdateView.as_view(), name="project-update"),
path("<str:owner>/<str:project_name>/delete/", views.ProjectDeleteView.as_view(), name="project-delete"),
path("", views.ProjectDetailView.as_view(), name="project-detail"),
path("edit/", views.ProjectUpdateView.as_view(), name="project-update"),
path("delete/", views.ProjectDeleteView.as_view(), name="project-delete"),
]

View File

@ -1,64 +1,84 @@
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.forms import inlineformset_factory
from django.db.models import Q
from django.shortcuts import redirect
from django.views.generic import CreateView, DeleteView, DetailView, UpdateView
from django.urls import reverse_lazy
from django_filters.views import FilterView
from django.views.generic import CreateView, DeleteView, DetailView, ListView, UpdateView
from .filters import ProjectFilter
from .forms import HostingPlatformForm, ProgrammingLanguageForm, ProjectForm
from .models import Project, ProjectProgrammingLanguage
ProgrammingLanguageInlineFormset = inlineformset_factory(
Project,
ProjectProgrammingLanguage,
form=ProgrammingLanguageForm,
extra=1,
)
from .forms import HostingPlatformForm, ProgrammingLanguageInlineFormSet, ProjectForm
from .models import Project
class ProjectListView(FilterView):
class SearchResultsListView(ListView):
model = Project
template_name = "search.html"
context_object_name = "projects"
def get_queryset(self):
query = self.request.GET.get("q")
return Project.objects.filter(
Q(owner__username__icontains=query)
| Q(name__icontains=query)
# | Q(description__icontains=query)
| Q(license__short_name__icontains=query)
| Q(license__full_name__icontains=query)
| Q(tag__name__icontains=query)
| Q(operating_system__operating_system__name__icontains=query)
| Q(operating_system__codename__icontains=query)
| Q(programming_language__name__icontains=query)
).distinct()
def get_context_data(self, *args, **kwargs):
data = super().get_context_data(**kwargs)
data["title"] = "FOSSDB | Search"
return data
class ProjectListView(ListView):
model = Project
template_name = "explore.html"
filterset_class = ProjectFilter
context_object_name = "projects"
paginate_by = 100 # optional 10 projects a page
paginate_by = 50 # amount of items on screen
def get_context_data(self, *args, **kwargs):
data = super().get_context_data(**kwargs)
data["title"] = "FOSSDB | Explore"
return data
class ProjectCreateView(LoginRequiredMixin, CreateView):
model = Project
form_class = ProjectForm
template_name = "create_view.html"
login_url = "/login/"
login_url = reverse_lazy("login")
redirect_field_name = "redirect_to"
def get_context_data(self, *args, **kwargs):
data = super().get_context_data(**kwargs)
data["hosting_platform"] = HostingPlatformForm(self.request.POST or None, prefix="hosting")
data["programming_language"] = ProgrammingLanguageInlineFormset(self.request.POST or None, prefix="language")
data["empty_form"] = ProgrammingLanguageInlineFormset(prefix="language_empty")
data["title"] = "FOSSDB | Create Project"
data["hosting_platform"] = HostingPlatformForm(self.request.POST or None)
data["programming_languages"] = ProgrammingLanguageInlineFormSet(self.request.POST or None)
return data
def form_valid(self, form):
context = self.get_context_data()
form.instance.owner = self.request.user
self.object = form.save() # Save project form
hosting_platform = context["hosting_platform"]
programming_language = context["programming_language"]
self.object = form.save()
if hosting_platform.is_valid():
hosting_platform.instance.project = self.object
hosting_platform = hosting_platform.save(commit=False)
hosting_platform.project = self.object
hosting_platform.save()
# TODO: allow adding multiple languages
if programming_language.is_valid():
for instance in programming_language.save(commit=False):
instance.project = self.object
instance.save()
programming_language.save_m2m()
if hosting_platform.is_valid() and programming_language.is_valid():
programming_languages = context["programming_languages"]
if programming_languages.is_valid():
programming_languages.instance = self.object
programming_languages.save()
return super().form_valid(form)
else:
return self.render_to_response(self.get_context_data(form=form))
class ProjectDetailView(DetailView):
@ -75,7 +95,7 @@ class ProjectUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
form_class = ProjectForm
slug_field = "name"
slug_url_kwarg = "project_name"
login_url = "/login/"
login_url = reverse_lazy("login")
redirect_field_name = "redirect_to"
def test_func(self):
@ -85,24 +105,48 @@ class ProjectUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
return redirect("login")
def get_context_data(self, *args, **kwargs):
data = super(ProjectUpdateView, self).get_context_data(**kwargs)
data["hosting_platform"] = HostingPlatformForm(self.request.POST or None, instance=self.object.projecthostingplatform, prefix="hosting")
data["programming_language"] = ProgrammingLanguageInlineFormset(self.request.POST or None, instance=self.object, prefix="language")
data["empty_form"] = ProgrammingLanguageInlineFormset(prefix="language_empty")
data = super().get_context_data(**kwargs)
data["title"] = f"Edit {self.object}"
data["hosting_platform"] = HostingPlatformForm(self.request.POST or None, instance=self.object.projecthostingplatform)
data["programming_languages"] = ProgrammingLanguageInlineFormSet(self.request.POST or None, instance=self.object)
return data
def form_valid(self, form):
context = self.get_context_data()
form.instance.owner = self.request.user
self.object = form.save() # Save project form
hosting_platform = context["hosting_platform"]
if hosting_platform.is_valid():
hosting_platform.project = self.object
hosting_platform.save()
programming_languages = context["programming_languages"]
if programming_languages.is_valid():
programming_languages.instance = self.object
programming_languages.save()
return super().form_valid(form)
class ProjectDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
model = Project
template_name = "delete_view.html"
slug_field = "name"
slug_url_kwarg = "project_name"
login_url = "/login/"
login_url = reverse_lazy("login")
redirect_field_name = "redirect_to"
success_url = "/"
success_url = reverse_lazy("explore")
def test_func(self):
return self.get_object().owner == self.request.user
def handle_no_permission(self):
return redirect("login")
def get_context_data(self, *args, **kwargs):
data = super().get_context_data(**kwargs)
data["title"] = f"Delete {self.object}"
return data

View File

@ -0,0 +1,5 @@
{% extends "base.html" %}
{% load static %}
{% block title %}{{ title }}{% endblock %}
{% block meta %}{% endblock %}
{% block content %}{% endblock %}

View File

@ -0,0 +1,5 @@
{% extends "base.html" %}
{% load static %}
{% block title %}{{ title }}{% endblock %}
{% block meta %}{% endblock %}
{% block content %}{% endblock %}

View File

@ -0,0 +1,5 @@
{% extends "base.html" %}
{% load static %}
{% block title %}{{ title }}{% endblock %}
{% block meta %}{% endblock %}
{% block content %}{% endblock %}

View File

@ -1,11 +1,20 @@
from account.views import ProfileProjectListView
from django.urls import path
from fossdb.views import ProjectCreateView, ProjectListView, SearchResultsListView
from . import views
urlpatterns = [
path("", views.homepage, name="homepage"),
path("contribute/", views.contribute, name="contribute"),
path("news/", views.news, name="news"),
path("search/", SearchResultsListView.as_view(), name="search"),
path("explore/", ProjectListView.as_view(), name="explore"),
path("contribute/", ProjectCreateView.as_view(), name="contribute"),
path("dashboard/", views.dashboard, name="dashboard"),
path("news/", views.news, name="news"),
path("help/", views.help, name="help"),
path("login/", views.login),
path("logout/", views.logout),
path("signup/", views.signup),
path("<str:username>/", ProfileProjectListView.as_view(), name="profile"),
]

View File

@ -1,10 +1,22 @@
from django.shortcuts import render
from django.shortcuts import redirect, render
def homepage(request):
return render(request, "homepage.html", {"title": "FOSSDB"})
def login(request):
return redirect("login")
def logout(request):
return redirect("logout")
def signup(request):
return redirect("signup")
def contribute(request):
return render(request, "contribute.html", {"title": "FOSSDB | Contribute"})

View File

@ -2,10 +2,6 @@
@tailwind components;
@tailwind utilities;
@import url("https://fonts.googleapis.com/css2?family=Abel&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Rationale&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Roboto+Condensed:ital,wght@0,300;0,400;0,700;1,300;1,400;1,700&display=swap");
body {
@apply leading-[normal] m-0;
}
@ -28,3 +24,27 @@ body {
background: linear-gradient(to right, transparent, #27a1b9, transparent);
transform: translateY(-50%);
}
.form-field {
@apply mt-1 block rounded-md border-2;
}
.submit-form {
@apply border-slategray-200 hover:border-lightsteelblue-100 bg-gray-300 placeholder-slategray-100 focus:border-cadetblue-300;
}
.checkbox-form {
@apply border-slategray-200 hover:border-lightsteelblue-100 bg-gray-300 placeholder-slategray-100 focus:border-cadetblue-300;
}
.button {
@apply font-condensed text-xl px-12 py-2 border rounded-md transform ease-linear duration-100;
}
.submit-button-disabled {
@apply opacity-60 cursor-default;
}
.submit-button-enabled {
@apply opacity-100 hover:opacity-60 hover:bg-opacity-60;
}

View File

@ -7,11 +7,11 @@
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Abel&family=Rationale&family=Roboto+Condensed:ital,wght@0,300;0,400;0,700;1,300;1,400;1,700&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap"
<link href="https://fonts.googleapis.com/css2?family=Roboto+Condensed:ital,wght@0,300;0,400;0,700;1,300;1,400;1,700&display=swap"
rel="stylesheet" />
<link href="https://fonts.googleapis.com/css2?family=Abel&family=Roboto+Condensed:ital,wght@0,300;0,400;0,700;1,300;1,400;1,700&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap"
<link href="https://fonts.googleapis.com/css2?family=Abel&display=swap"
rel="stylesheet" />
<link href="https://fonts.googleapis.com/css2?family=Roboto+Condensed:ital,wght@0,300;0,400;0,700;1,300;1,400;1,700&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap"
<link href="https://fonts.googleapis.com/css2?family=Rationale&display=swap"
rel="stylesheet" />
<link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap"
rel="stylesheet" />
@ -27,32 +27,35 @@
<header class="flex justify-between items-center px-6 py-2 font-abel text-xl">
<div class="flex justify-between">
<!-- logo -->
<div class="flex items-center text-4xl font-abel">
<div class="flex items-center text-4xl font-rationale">
<img class=""
width="40"
src="{% static 'img/icons/logo.svg' %}"
alt="logo" />
<a class="hover:text-skyblue-300 transform duration-300 ease-in-out"
<a class="hover:text-skyblue-300 transform duration-300 ease-linear"
href="{% url 'homepage' %}">foss<span class="text-skyblue-300">db</span></a>
</div>
<!-- search -->
<div class="relative items-center flex">
<form action="{% url 'search' %}"
method="get"
class="relative items-center flex">
<input type="text"
name="q"
placeholder="Search projects..."
class="py-2 mx-4 placeholder-slategray-100 bg-gray-300 border-0 border-b-2 border-b-slategray-100 hover:border-b-lightsteelblue-100 transform duration-200 ease-in-out focus:outline-none focus:border-b-skyblue-300 focus:border-b-2 max-w-[10rem]" />
</div>
class="py-2 mx-4 placeholder-slategray-100 bg-gray-300 border-0 border-b-2 border-b-slategray-100 hover:border-b-lightsteelblue-100 transform duration-200 ease-linear focus:outline-none focus:border-b-skyblue-300 focus:border-b-2 max-w-[10rem]" />
</form>
<!-- navbar -->
<nav class="uppercase flex gap-x-6 items-center">
<a href="{% url 'explore' %}"
class="hover:text-skyblue-300 transform duration-200 ease-in-out">explore</a>
class="hover:text-skyblue-300 transform duration-200 ease-linear">explore</a>
<a href="{% url 'contribute' %}"
class="hover:text-skyblue-300 transform duration-200 ease-in-out">contribute</a>
class="hover:text-skyblue-300 transform duration-200 ease-linear">contribute</a>
<a href="{% url 'news' %}"
class="hover:text-skyblue-300 transform duration-200 ease-in-out">news</a>
class="hover:text-skyblue-300 transform duration-200 ease-linear">news</a>
<a href="{% url 'dashboard' %}"
class="hover:text-skyblue-300 transform duration-200 ease-in-out">dashboard</a>
class="hover:text-skyblue-300 transform duration-200 ease-linear">dashboard</a>
<a href="{% url 'help' %}"
class="hover:text-skyblue-300 transform duration-200 ease-in-out">help</a>
class="hover:text-skyblue-300 transform duration-200 ease-linear">help</a>
</nav>
</div>
<div class="flex items-center justify-between">
@ -61,14 +64,12 @@
<div class="flex items-center justify-between">
<p class="mx-4">{{ user.username }}</p>
<a href="{% url 'profile' user.username %}">
<img src="{{ user.profile.picture.url }}"
class="w-[2rem]"
alt="{{ user.username }}'s profile picture" />
<img src="{{ user.profile.picture.url }}" class="w-[2rem]" alt="pic" />
</a>
</div>
{% else %}
<a href="{% url 'login' %}"
class="hover:text-skyblue-300 transition duration-300 ease-in-out"><i class="fa-solid fa-user fa-lg ml-4 "></i></a>
class="hover:text-skyblue-300 transition duration-300 ease-linear"><i class="fa-solid fa-user fa-lg ml-4 "></i></a>
{% endif %}
</div>
</header>
@ -81,10 +82,9 @@
<p>
FOSSDB is a passion project of <a class="underline"
href="https://github.com/kristoferssolo"
target="_blank">@kristoferssolo</a> and a dedicated community of reporters.
target="_blank">@kristoferssolo</a>.
</p>
<!-- TODO: finish these sentences -->
<p>This site uses data from GitHub as well as data...</p>
<p>This site has no affiliation with GitHub or any other hosting platform.</p>
</div>
<div></div>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

View File

@ -1,3 +1,11 @@
Array.from(document.getElementsByClassName("remove-btn")).forEach((button) => {
button.addEventListener("click", () => {
console.log("TRUE")
const hiddenDeleteInput = button.previousElementSibling
hiddenDeleteInput.value = "on"
button.parentElement.style.display = "none"
})
})
window.addEventListener("DOMContentLoaded", () => {
const FORM_VERIFY = document.getElementsByClassName("verify")
const SUBMIT_BUTTON = document.getElementById("submit-button")
@ -10,30 +18,26 @@ window.addEventListener("DOMContentLoaded", () => {
)
if (ALL_FILLED) {
SUBMIT_BUTTON.classList.remove(
"bg-slategray-200",
"submit-button-disabled",
"text-lightsteelblue-100",
"opacity-50",
"cursor-default"
"bg-slategray-200"
)
SUBMIT_BUTTON.classList.add(
"bg-skyblue-300",
"submit-button-enabled",
"text-gray-500",
"opacity-100",
"hover:opacity-60"
"bg-skyblue-300"
)
SUBMIT_BUTTON.disabled = false
} else {
SUBMIT_BUTTON.classList.remove(
"bg-skyblue-300",
"submit-button-enabled",
"text-gray-500",
"opacity-100",
"hover:opacity-60"
"bg-skyblue-300"
)
SUBMIT_BUTTON.classList.add(
"bg-slategray-200",
"submit-button-disabled",
"text-lightsteelblue-100",
"opacity-50",
"cursor-default"
"bg-slategray-200"
)
SUBMIT_BUTTON.disabled = true
}

24
tox.ini
View File

@ -1,24 +0,0 @@
[tox]
minversion = 3.8.0
envlist = django, flake8, mypy
isolated_build = true
[gh-actions]
python =
3.10: py310, mypy, flake8
[testenv:django]
basepython = python3.10
deps = django
commands = python manage.py test
[testenv:flake8]
basepython = python3.10
deps = flake8
commands = flake8 FOSSDB_web
[testenv:mypy]
basepython = python3.10
deps =
-r{toxinidir}/requirements_dev.txt
commands = mypy FOSSDB_web