Skip to content

Conversation

@ahtesham-quraish
Copy link
Contributor

@ahtesham-quraish ahtesham-quraish commented Dec 9, 2025

What are the relevant tickets?

N/A

Description (What does it do?)

This feature is about adding slug, Have added the backend slug field and frontend will convert the title into slug on its first published. We have introduced new api which will return the article detail based on slug or id of the article.

Screenshots (if appropriate):

Screen.Recording.2025-12-09.at.2.12.16.PM.mov

How can this be tested?

  • Need to go to new article url
  • As long as you have not published the article the slug will not be created once article is published then slug will be created and saved in backed
  • You need to test the copy paste into editor as well because it was bug in code which I resolved
  • For Image you just need to upload image and see if it is small is then it should not have resizable buttons except the default one if it is bigger image then all three resizable buttons should be available on hover the image. basically we tried to restrict the image not too stretched

Additional Context

@github-actions
Copy link

github-actions bot commented Dec 9, 2025

OpenAPI Changes

Show/hide 11 changes: 0 error, 0 warning, 11 info
11 changes: 0 error, 0 warning, 11 info
info	[response-optional-property-added] at head/openapi/specs/v1.yaml	
	in API GET /api/v1/articles/
		added the optional property 'results/items/slug' to the response with the '200' status

info	[new-optional-request-property] at head/openapi/specs/v1.yaml	
	in API POST /api/v1/articles/
		added the new optional request property 'slug'

info	[new-optional-request-property] at head/openapi/specs/v1.yaml	
	in API POST /api/v1/articles/
		added the new optional request property 'slug'

info	[new-optional-request-property] at head/openapi/specs/v1.yaml	
	in API POST /api/v1/articles/
		added the new optional request property 'slug'

info	[response-optional-property-added] at head/openapi/specs/v1.yaml	
	in API POST /api/v1/articles/
		added the optional property 'slug' to the response with the '201' status

info	[endpoint-added] at head/openapi/specs/v1.yaml	
	in API GET /api/v1/articles/detail/{identifier}/
		endpoint added

info	[response-optional-property-added] at head/openapi/specs/v1.yaml	
	in API GET /api/v1/articles/{id}/
		added the optional property 'slug' to the response with the '200' status

info	[new-optional-request-property] at head/openapi/specs/v1.yaml	
	in API PATCH /api/v1/articles/{id}/
		added the new optional request property 'slug'

info	[new-optional-request-property] at head/openapi/specs/v1.yaml	
	in API PATCH /api/v1/articles/{id}/
		added the new optional request property 'slug'

info	[new-optional-request-property] at head/openapi/specs/v1.yaml	
	in API PATCH /api/v1/articles/{id}/
		added the new optional request property 'slug'

info	[response-optional-property-added] at head/openapi/specs/v1.yaml	
	in API PATCH /api/v1/articles/{id}/
		added the optional property 'slug' to the response with the '200' status


Unexpected changes? Ensure your branch is up-to-date with main (consider rebasing).

@ahtesham-quraish ahtesham-quraish force-pushed the ahtesham/slug-feature branch 2 times, most recently from f4fbe54 to f3561d4 Compare December 9, 2025 08:35
@ahtesham-quraish ahtesham-quraish changed the title Ahtesham/slug feature feature: add slug for article feature Dec 9, 2025
Copy link
Contributor

@ChristopherChudzicki ChristopherChudzicki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a few comments that I think would simplify this.

Also, could you add a test for retreival by slug to views_test.py ?

return super().destroy(request, *args, **kwargs)


class ArticleDetailByIdOrSlugAPIView(APIView):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Request: Rather than a new APIView, I think we should reuse the existing one in order to share queryset and permissions. Two options:

  1. Override get_object to accept id or slug
  2. Add a new action

(1) seems simpler since fewer APIs... was thinking something like

    def get_object(self):
        """
        Override to allow lookup by either ID (numeric) or slug.
        """
        queryset = self.filter_queryset(self.get_queryset())
        identifier = self.kwargs.get("pk")
        if str(identifier).isdigit():
            filter_kwargs = {"pk": int(identifier)}
        else:
            filter_kwargs = {"slug": identifier}

        obj = get_object_or_404(queryset, **filter_kwargs)

        self.check_object_permissions(self.request, obj)

        return obj

Copy link
Contributor Author

@ahtesham-quraish ahtesham-quraish Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have tried to use the existing but one issue was the id remains compulsory which is number and generated by DRF and instead of changing the existing because it can be used later on for any other feature I thought we can create new api. We are applying the same permissions on the this new api.

export interface ArticlesApiArticlesRetrieveRequest {
  /**
   * A unique integer value identifying this article.
   * @type {number}
   * @memberof ArticlesApiArticlesRetrieve
   */
  readonly id: number
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After discussion, this was moved into an @action in order to reuse queryset and permissions.

}
}

export const slugify = (title: string) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would lean toward slugifying on the backend... We usually use django's slugify util.

I noticed that the current slug field is also nullable. We could make it required and set it to the title always pre-publish:

from django.utils.text import slugify

class Article(TimestampedModel):

    def save(self, *args, **kwargs):
        previous = Article.objects.get(pk=self.pk) if self.pk else None
        was_published = getattr(previous, "is_published", None)
        if not was_published:
            self.slug = slugify(self.title)
        super().save(*args, **kwargs)

May also want to deal with duplicate slugs (that seems easier on the backend).

Copy link
Contributor Author

@ahtesham-quraish ahtesham-quraish Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have incorporated the above code and removed slugify from frontend. we have created the slug field with unique constraint and slug will be created only once so why we need to deal with duplicate? btw I have dealth with duplicate check the logic now please

slug = models.SlugField(max_length=255, unique=True, blank=True, null=True)

@ahtesham-quraish
Copy link
Contributor Author

Also, could you add a test for retreival by slug to views_test.py ?

I have added the unit tests

@ahtesham-quraish ahtesham-quraish force-pushed the ahtesham/slug-feature branch 4 times, most recently from 95ad0f2 to 9fdf692 Compare December 11, 2025 16:04
Copy link
Contributor

@ChristopherChudzicki ChristopherChudzicki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The slug changes look good 👍

Could you fix the linting issue and also either

  • move image changes to separate PR (preferred)
  • or add testing instructions for them (is there an issue?)

@ahtesham-quraish
Copy link
Contributor Author

ahtesham-quraish commented Dec 11, 2025

The slug changes look good 👍

Could you fix the linting issue and also either

Done

add testing instructions for them (is there an issue?)

I have added the instructions check if those make sense

Copy link
Contributor

@ChristopherChudzicki ChristopherChudzicki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noticed a couple issues with the image uploading.

Suggestion: Move the image-related changes to a separate branch, we can merge the slug stuff as-is.

In general, it would be good to do distinct features on distinct branches.

const imageNaturalWidth = img.naturalWidth

// If the image can't expand beyond the container, disable wide/full
setCanExpand(imageNaturalWidth > containerWidth)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the image can't expand beyond the container, disable wide/full

Comparing the current container width and the image width isn't a reliable way to achieve this goal since the container width depends on the screen size. I see you added an event listener for window resize events, but that doesn't entirely solve the problem:

  1. user uploads a small image on small screen and selects option to make it full width
  2. image node data now has layout: "full".
  3. window resizes
  4. controls UI now removes the the full/wide options, but node still says "full"
    • changing the node data on resize wouldn't help. The node data should be static. It should not depend on user's screen size.

My suggestion would be: compare to a constant value, like 900px, not to the screen size. The available options (and especially the node data) should not depend on user's screen size.

900px is our medium breakpoint, though potentially we want a higher value—full width images should be higher res—but that can always be tweaked in future.

alt={alt || caption}
layout={layout}
ref={imgRef}
className={`${!canExpand ? "img-contained" : ""}`}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not do className={${!canExpand ? "img-contained" : ""}}.

The display of an image should only depend on the node attrs, not on the non-node state.

This leads to some odd behavior right now where:

  1. Insert a small image
  2. It looks ok, good.
  3. Delete it
  4. Press undo
  5. Now the image looks huge. (I guess the value of canExpand is wrong in this scenario).

The above would be avoided if the display is a function of only the node attributes.

Suggestion: Should this instead be based on node.layout === "default" ?

<Image src={src} alt={alt || caption} layout={layout} />
<Image
src={src}
alt={alt || caption}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A screenreader will read the alt text then the caption, so using the caption as a fallback for the alt text doesn't seem beneficial to me. It would just result in the caption being read twice.

I'd leave this as alt={alt}; if there's no alt text...well, at least it will read the caption.

(That said, at some point, we should figure out how to make alt text required.)


window.addEventListener("resize", checkSize)
return () => window.removeEventListener("resize", checkSize)
}, [src])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW: I'm not sure you need useEffect here. I think passing the relevant logic as an onLoad callback to the Image would be fine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants