diff --git a/backend/api/conferences/types.py b/backend/api/conferences/types.py index 70b9df3a90..2f1076cd67 100644 --- a/backend/api/conferences/types.py +++ b/backend/api/conferences/types.py @@ -378,7 +378,7 @@ def is_running(self, info: Info) -> bool: @strawberry.field def sponsor_benefits(self) -> list[SponsorBenefit]: - benefits = self.sponsor_benefits.all() + benefits = self.sponsor_benefits.order_by("order").all() return [ SponsorBenefit( @@ -391,9 +391,13 @@ def sponsor_benefits(self) -> list[SponsorBenefit]: @strawberry.field def sponsor_levels(self) -> list[SponsorLevel]: - levels = SponsorLevelModel.objects.filter(conference=self).prefetch_related( - "sponsorlevelbenefit_set", - "sponsorlevelbenefit_set__benefit", + levels = ( + SponsorLevelModel.objects.filter(conference=self) + .prefetch_related( + "sponsorlevelbenefit_set", + "sponsorlevelbenefit_set__benefit", + ) + .order_by("order") ) return [ @@ -416,7 +420,7 @@ def sponsor_levels(self) -> list[SponsorLevel]: @strawberry.field def sponsor_special_options(self) -> list[SponsorSpecialOption]: - options = self.sponsor_special_options.all() + options = self.sponsor_special_options.order_by("order").all() return [ SponsorSpecialOption( diff --git a/backend/sponsors/admin.py b/backend/sponsors/admin.py index 52aa50766b..6055c8e4b4 100644 --- a/backend/sponsors/admin.py +++ b/backend/sponsors/admin.py @@ -22,9 +22,12 @@ class SponsorAdmin(OrderedModelAdmin): @admin.register(SponsorBenefit) -class SponsorBenefitAdmin(admin.ModelAdmin): - list_display = ("name", "conference", "category") - list_filter = ("category",) +class SponsorBenefitAdmin(OrderedModelAdmin, admin.ModelAdmin): + list_display = ("name", "conference", "category", "order", "move_up_down_links") + list_filter = ( + "conference", + "category", + ) class SponsorLevelBenefitInline(admin.TabularInline): @@ -43,8 +46,8 @@ class SponsorLevelAdmin(OrderedModelAdmin): @admin.register(SponsorSpecialOption) -class SponsorSpecialOptionAdmin(admin.ModelAdmin): - list_display = ("name", "conference", "price") +class SponsorSpecialOptionAdmin(OrderedModelAdmin, admin.ModelAdmin): + list_display = ("name", "conference", "price", "move_up_down_links") list_filter = ("conference",) diff --git a/backend/sponsors/management/commands/fill_sponsor_data.py b/backend/sponsors/management/commands/fill_sponsor_data.py index 005963ed2a..b012b61aa8 100644 --- a/backend/sponsors/management/commands/fill_sponsor_data.py +++ b/backend/sponsors/management/commands/fill_sponsor_data.py @@ -25,43 +25,43 @@ def handle(self, *args, **kwargs): levels = [ { "name": "Keystone", - "price": "€ 10,000", + "price": 10_000, "slots": 1, "highlight_color": "blue", }, { "name": "Gold", - "price": "€ 7,000", + "price": 7_000, "slots": 2, "highlight_color": "yellow", }, { "name": "Silver", - "price": "€ 5,000", + "price": 5_000, "slots": 5, "highlight_color": "gray", }, { "name": "Bronze", - "price": "€ 3,000", + "price": 3_000, "slots": 0, "highlight_color": "brown", }, { "name": "Patron", - "price": "€ 1,000", + "price": 2_000, "slots": 0, "highlight_color": "purple", }, { "name": "Startup", - "price": "€ 500", + "price": 500, "slots": 0, "highlight_color": "green", }, { "name": "Diversity", - "price": "€ 1,000", + "price": 1_000, "slots": 0, "highlight_color": "pink", }, diff --git a/backend/sponsors/migrations/0014_alter_sponsorbenefit_options_and_more.py b/backend/sponsors/migrations/0014_alter_sponsorbenefit_options_and_more.py new file mode 100644 index 0000000000..9ef28857bc --- /dev/null +++ b/backend/sponsors/migrations/0014_alter_sponsorbenefit_options_and_more.py @@ -0,0 +1,33 @@ +# Generated by Django 5.1.1 on 2024-11-09 18:30 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('sponsors', '0013_sponsorlevel_price_sponsorlevel_slots_and_more'), + ] + + operations = [ + migrations.AlterModelOptions( + name='sponsorbenefit', + options={'ordering': ('order',), 'verbose_name': 'sponsor benefit', 'verbose_name_plural': 'sponsor benefits'}, + ), + migrations.AlterModelOptions( + name='sponsorspecialoption', + options={'ordering': ('order',), 'verbose_name': 'special option', 'verbose_name_plural': 'special options'}, + ), + migrations.AddField( + model_name='sponsorbenefit', + name='order', + field=models.PositiveIntegerField(db_index=True, default=0, editable=False, verbose_name='order'), + preserve_default=False, + ), + migrations.AddField( + model_name='sponsorspecialoption', + name='order', + field=models.PositiveIntegerField(db_index=True, default=0, editable=False, verbose_name='order'), + preserve_default=False, + ), + ] diff --git a/backend/sponsors/models.py b/backend/sponsors/models.py index 76667575a6..4aaee220c8 100644 --- a/backend/sponsors/models.py +++ b/backend/sponsors/models.py @@ -71,13 +71,7 @@ class Meta(OrderedModel.Meta): unique_together = ["name", "conference"] -class SponsorBenefit(TimeStampedModel): - conference = models.ForeignKey( - "conferences.Conference", - on_delete=models.CASCADE, - related_name="sponsor_benefits", - ) - +class SponsorBenefit(OrderedModel, TimeStampedModel): class Category(models.TextChoices): CONTENT = "content", _("Sponsored Content") BOOTH = "booth", _("Booth") @@ -86,11 +80,19 @@ class Category(models.TextChoices): RECRUITING = "recruiting", _("Recruiting") ATTENDEE_INTERACTION = "attendee_interaction", _("Attendee Interaction") + conference = models.ForeignKey( + "conferences.Conference", + on_delete=models.CASCADE, + related_name="sponsor_benefits", + ) + name = I18nCharField(_("name"), max_length=100) category = models.CharField(_("category"), max_length=100, choices=Category.choices) description = I18nTextField(_("description"), blank=True) - class Meta: + order_with_respect_to = "conference" + + class Meta(OrderedModel.Meta): unique_together = ["name", "conference"] verbose_name = _("sponsor benefit") verbose_name_plural = _("sponsor benefits") @@ -119,7 +121,7 @@ def __str__(self): return f"{self.sponsor_level} - {self.benefit} ({self.value})" -class SponsorSpecialOption(models.Model): +class SponsorSpecialOption(OrderedModel, models.Model): conference = models.ForeignKey( "conferences.Conference", on_delete=models.CASCADE, @@ -128,8 +130,9 @@ class SponsorSpecialOption(models.Model): name = models.CharField(_("name"), max_length=255) description = models.TextField(_("description")) price = models.DecimalField(_("price"), max_digits=10, decimal_places=2) + order_with_respect_to = "conference" - class Meta: + class Meta(OrderedModel.Meta): verbose_name = _("special option") verbose_name_plural = _("special options") unique_together = ["name", "conference"] diff --git a/frontend/src/components/brochure/index.tsx b/frontend/src/components/brochure/index.tsx index 1443466f72..36e6f169fa 100644 --- a/frontend/src/components/brochure/index.tsx +++ b/frontend/src/components/brochure/index.tsx @@ -55,12 +55,12 @@ export function Brochure({ /> diff --git a/frontend/src/components/brochure/options-page.tsx b/frontend/src/components/brochure/options-page.tsx index d8056bf783..25ce167329 100644 --- a/frontend/src/components/brochure/options-page.tsx +++ b/frontend/src/components/brochure/options-page.tsx @@ -1,4 +1,6 @@ import clsx from "clsx"; +import { compile } from "~/helpers/markdown"; +import { humanizeText } from "./utils"; export const OptionsPage = ({ title, @@ -17,12 +19,12 @@ export const OptionsPage = ({ {options.map((option) => (
- {option.name} + {humanizeText(option.name)} {option.price && ( - {option.price}€ )}
-
{option.description}
+
{compile(option.description).tree}
))} diff --git a/frontend/src/components/brochure/pricing-page.tsx b/frontend/src/components/brochure/pricing-page.tsx index 448c83c6ec..1f38ee396e 100644 --- a/frontend/src/components/brochure/pricing-page.tsx +++ b/frontend/src/components/brochure/pricing-page.tsx @@ -1,4 +1,5 @@ import clsx from "clsx"; +import { humanizeText } from "./utils"; export type Benefit = { name: string; @@ -35,7 +36,7 @@ const TableSection = ({ return ( - {title} + {humanizeText(title)} {new Array(totalPackages).fill(null).map((_, i) => ( diff --git a/frontend/src/components/brochure/utils.ts b/frontend/src/components/brochure/utils.ts new file mode 100644 index 0000000000..dc04a2ef92 --- /dev/null +++ b/frontend/src/components/brochure/utils.ts @@ -0,0 +1,3 @@ +export const humanizeText = (text: string) => { + return text.replace("_", " "); +}; diff --git a/frontend/src/components/brochure/why-sponsor-page.tsx b/frontend/src/components/brochure/why-sponsor-page.tsx index b0b8dc45a5..3952801528 100644 --- a/frontend/src/components/brochure/why-sponsor-page.tsx +++ b/frontend/src/components/brochure/why-sponsor-page.tsx @@ -1,4 +1,4 @@ -import { SnakeDNA } from "@python-italia/pycon-styleguide/illustrations"; +import { Text } from "@python-italia/pycon-styleguide"; import { compile } from "~/helpers/markdown"; const Snake1 = (props: React.SVGProps) => ( @@ -192,9 +192,16 @@ export function WhySponsorPage({ -

- {compile(whySponsor.text).tree} -

+
+
+ + Get in touch, we’d be happy to take care of your ideas! + +
+

+ {compile(whySponsor.text).tree} +

+
); diff --git a/frontend/src/pages/brochure/index.tsx b/frontend/src/pages/brochure/index.tsx index ed39e072f1..db75868330 100644 --- a/frontend/src/pages/brochure/index.tsx +++ b/frontend/src/pages/brochure/index.tsx @@ -17,17 +17,17 @@ const testimonials = [ ]; const stats = { - attendees: "800+", + attendees: "1000+", speakers: "100+", talks: "100+", uniqueOnlineVisitors: "10000+", sponsorsAndPartners: "50+", - grantsGiven: "10+", - coffees: "1000+", + grantsGiven: "15+", + coffees: "10000+", }; const introduction = ` -**PyCon Italia** is the official Italian event about Python, but nowadays it's one of the most important pythonic events in all of Europe. More than 800 people gather from all over the world to attend, learn, code, speak, support, and meet other fellow pythonistas in Florence. +**PyCon Italia** is the official Italian event about Python, but nowadays it's one of the most important pythonic events in all of Europe. More than 1000 people gather from all over the world to attend, learn, code, speak, support, and meet other fellow pythonistas in Bologna. Our care for the quality of every aspect of PyCon Italia results in a wonderful gathering for growing together. @@ -50,7 +50,7 @@ Bologna is one of the most charming cities of Italy, and we love it. Many of our Included in the UNESCO Creative Cities Network as a City of Music, the historic center of Bologna is a treasure trove of art and architecture. -It has to be told that the PyCon Italia venue is located very close to the city center (10 minutes by walk) and many initiatives will be announced for sharing this treasure with our attendees. +The PyCon Italia venue is located close to the city center (~1h walk or ~20' bus) and many initiatives will be announced for sharing this treasure with our attendees. `.trim(), country: "Italy", @@ -60,7 +60,7 @@ It has to be told that the PyCon Italia venue is located very close to the city "https://images.unsplash.com/photo-1671794646570-cba0e7dc162b?q=80&w=2670&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", }; const community = ` -PyCon Italia is aimed at everyone in the Python community, of all skill levels, both users and programmers. It is a great meeting event: ~800 attendees are expected from all over the world. Professionals, companies and students will meet for learning, collaborate and grow together. The delegates are a mix of Python users and developers (~60%), students (~20%), PMs (~8%), researchers (~7%), CTOs (~5%) as well as individuals whose businesses rely on the use of Python. +PyCon Italia is aimed at everyone in the Python community, of all skill levels, both users and programmers. It is a great meeting event: ~1000 attendees are expected from all over the world. Professionals, companies and students will meet for learning, collaborate and grow together. The delegates are a mix of Python users and developers (~60%), students (~20%), PMs (~8%), researchers (~7%), CTOs (~5%) as well as individuals whose businesses rely on the use of Python. `.trim(); const whySponsor = {