Implemented search

This commit is contained in:
Kristofers Solo 2023-06-29 16:41:10 +00:00
parent 7e8bf84c0b
commit 313ff02f25
16 changed files with 250 additions and 108 deletions

View File

@ -2,6 +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-crispy-forms==2.0
django-search-views-0.3.7

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

@ -24,7 +24,7 @@
id="submit-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 hover:text-cadetblue-300 transform duration-300 ease-in-out"
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>

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,20 +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": "",
}
@ -28,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=1,
can_delete=True,
)
class ProjectForm(forms.ModelForm):
@ -52,29 +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(),
}
ProgrammingLanguageInlineFormSet = inlineformset_factory(
Project,
ProjectProgrammingLanguage,
fields=(
"programming_language",
"percentage",
),
extra=1,
can_delete=True,
)
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

@ -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)
@ -117,7 +117,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}%"

View File

@ -13,33 +13,33 @@
</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
<a href="/search/?q=linux"><i class="fa-brands fa-linux fa-lg hover:text-lightsteelblue-200 transform duration-300 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
<a href="/search/?q=windows"><i class="fa-brands fa-windows fa-lg hover:text-lightsteelblue-200 transform duration-300 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
<a href="/search/?q=macos"><i class="fa-brands fa-apple fa-lg hover:text-lightsteelblue-200 transform duration-300 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
<a href="/search/?q=ios"><i id="ios"
class="fa-brands fa-app-store-ios fa-lg hover:text-lightsteelblue-200 transform duration-300 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
<a href="/search/?q=android"><i id="android"
class="fa-brands fa-android fa-lg hover:text-lightsteelblue-200 transform duration-300 ease-linear
{% if project.runs_on_android %}
text-lightsteelblue-100
{% else %}
@ -49,9 +49,9 @@
<!-- 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-300 ease-linear">
{{ tag }}
</span>
</a>
@ -60,9 +60,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-300 ease-linear">
{{ language.programming_language }}
</span>
</a>
@ -75,7 +75,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-300 ease-linear"
id="menu-button"
aria-haspopup="true"
aria-expanded="true">Licenses</button>
@ -83,7 +83,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-300 ease-linear"
href="{{ project.projecthostingplatform.url }}"
target="_blank">here</a>!
</p>

View File

@ -3,13 +3,20 @@
{% block title %}{{ title }}{% endblock %}
{% block meta %}{% endblock %}
{% block content %}
{% for project in projects %}
<div>
<a href="{{ project.get_absolute_url }}">{{ project }}</a>
<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 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>
{% empty %}
<p>No projects yet.</p>
{% endfor %}
</div>
<div>{{ project.description }}</div>
<hr />
{% empty %}
<p>No projects found</p>
{% endfor %}
</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-skyblue-300 p-8 flex flex-row gap-4 justify-between">
<a href="{{ project.get_absolute_url }}"
class="hover:text-skyblue-300 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>
{% empty %}
<p>No projects yet.</p>
{% endfor %}
</div>
</div>
{% endblock %}

View File

@ -3,6 +3,7 @@ from django.urls import path
from . import views
urlpatterns = [
path("search/", views.SearchResultsListView.as_view(), name="search"),
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"),

View File

@ -1,21 +1,39 @@
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
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 .filters import ProjectFilter
from django.views.generic import CreateView, DeleteView, DetailView, ListView, UpdateView
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()
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
class ProjectCreateView(LoginRequiredMixin, CreateView):
@ -108,7 +126,7 @@ class ProjectDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
slug_url_kwarg = "project_name"
login_url = "/login/"
redirect_field_name = "redirect_to"
success_url = "/"
success_url = reverse_lazy("homepage")
def test_func(self):
return self.get_object().owner == self.request.user

View File

@ -38,7 +38,7 @@ body {
}
.button {
@apply font-abel px-12 py-2 border rounded-md transform ease-in-out duration-500;
@apply font-abel px-12 py-2 border rounded-md transform ease-linear duration-500;
}
.submit-button-disabled {

View File

@ -32,27 +32,30 @@
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">
@ -68,7 +71,7 @@
</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>