User Specific Data Rendering Full Stack

56 Views Asked by At

I am creating a full stack web application using React and Django that allows a user to upload an XML file which is then converted into and rendered as multiple JSON objects. Because the files are so large, I have implemented pagination.

Currently, I am running into an issue of trying to get the data to be user specific. I can upload a file, but the same data is rendering on multiple users' profiles.

Here is my relevant code:

API Calls

const getFile = (uid) =>
  new Promise((resolve, reject) => {
    fetch(`${clientCredentials.databaseURL}/files`)
      .then((response) => response.json())
      .then(resolve)
      .then(reject);
  });

const createFile = (formData) =>
  new Promise((resolve, reject) => {
    fetch(`${clientCredentials.databaseURL}/files/`, {
      method: 'POST',
      // headers: {
      //   'Content-Type': 'multipart/form-data',
      //   Accept: 'application/json',
      //   // type: 'formData',
      // },
      body: formData,
    })
      .then((response) => resolve(response.json()))
      .catch((error) => reject(error));
  });

File Form

const initialState = {
  xml: null,
  log: {
    dateStart: '',
    dateEnd: '',
    dateGenerated: '',
    user: 1,
  },
  entry: {
    type: '',
    date: '',
    details: '',
    zczc: '',
  },
};

function FileForm({ fileObj }) {
  const [fileList, setFileList] = useState(initialState);

  const router = useRouter();

  const { user } = useAuth();
  console.warn(user);

  useEffect(() => {
    if (fileObj.xml) {
      setFileList((prevState) => ({
        ...prevState,
        xml: fileObj.xml,
      }));
    }
  }, [fileObj]);

  // XML READER FUNCTIONALITY
  function xmlToJson(xmlDoc) {
    const result = {};

    if (xmlDoc.hasChildNodes()) {
      for (let i = 0; i < xmlDoc.childNodes.length; i++) {
        const item = xmlDoc.childNodes[i];

        if (item.nodeType === 1) {
          // Element node
          result[item.nodeName] = xmlToJson(item);
        } else if (item.nodeType === 3) {
          // Text node
          result.value = item.textContent;
        }
      }
    }

    return result;
  }

  const handleFileChange = (e) => {
    const file = e.target.files[0];
    const reader = new FileReader();

    reader.onload = () => {
      const xmlString = reader.result;
      const parser = new DOMParser();
      const xmlDoc = parser.parseFromString(xmlString, 'text/xml');
      const jsonData = xmlToJson(xmlDoc);

      setFileList({
        xml: xmlString,
        log: {
          dateStart: jsonData.log.dateStart.value,
          dateEnd: jsonData.log.dateEnd.value,
          dateGenerated: jsonData.log.dateGenerated.value,
        },
        entry: {
          type: jsonData.log.entry.type.value,
          date: jsonData.log.entry.date.value,
          details: jsonData.log.entry.details.value,
          zczc: jsonData.log.entry.zczc.value,
        },
      });
    };

    reader.readAsText(file);
  };

  // new
  const handleUploadClick = (e) => {
    e.preventDefault();
    const { xml, log, entry } = fileList;

    if (!xml || !log || !entry) {
      return; // Exit if any of the required fields is missing
    }

    const parsedDate = new Date(entry.date); // Assuming entry.date is the date value from XML
    const isoFormattedDate = parsedDate.toISOString();

    const formData = new FormData();
    formData.append('xml', new File([xml], 'filename.xml')); // Append the file directly
    // formData.append('xml', xml, 'filename.xml'); // Append the file directly
    formData.append('log', JSON.stringify(log));
    console.warn(log); // Convert log object to JSON string and append
    // formData.append('entry', JSON.stringify(entry)); // Convert entry object to JSON string and append
    formData.append('entry', JSON.stringify({ ...entry, date: isoFormattedDate }));
    formData.append('uid', user && user.uid);
    console.warn(user.uid);
    console.warn('this is form data', ...formData);
    createFile(formData)
      .then(() => router.push('/log'))
      .catch((error) => console.error(error));
  };

  return (
    <div>
      <input type="file" onChange={handleFileChange} />

      {fileList && (
        <ul>
          <li>
            {fileList.name} - {fileList.type}
          </li>
        </ul>
      )}

      <Button type="submit" onClick={handleUploadClick}>
        Upload
      </Button>
      {fileList && <FileCard user={user} fileObj={fileList} onUpdate={viewFileDetails} />}
    </div>
  );
}

FileForm.propTypes = {
  user: PropTypes.shape({
    uid: PropTypes.string,
  }).isRequired,
  fileObj: PropTypes.shape({
    id: PropTypes.number,
    xml: PropTypes.string,
    log: PropTypes.shape({
      dateStart: PropTypes.string,
      dateEnd: PropTypes.string,
      dateGenerated: PropTypes.string,
    }),
    entry: PropTypes.shape({
      type: PropTypes.string,
      date: PropTypes.string,
      details: PropTypes.string,
      zczc: PropTypes.string,
    }),
    // user: PropTypes.shape({
    //   id: PropTypes.number,
    //   name: PropTypes.string,
    //   email: PropTypes.string,
    //   uid: PropTypes.string,
    // }),
  }),
};
console.warn('These are proptypes', FileForm.propTypes);

FileForm.defaultProps = {
  fileObj: initialState,
};

export default FileForm;

Log.js

const LogPage = () => {
  const [files, setFiles] = useState([]);
  const [page, setPage] = useState(1);
  const [hasNext, setHasNext] = useState(false);
  // const [filteredFiles, setFilteredFiles] = useState([]);

  const { user } = useAuth();

  // new
  const getAllFiles = (pageNumber) => {
    fetch(`${clientCredentials.databaseURL}/files/?page=${pageNumber}&page_size=50`)
      .then((response) => response.json())
      .then((data) => {
        setFiles(data.results);
        setHasNext(data.next !== null);
      })
      .catch((error) => console.error(error));
  };

  useEffect(() => {
    getAllFiles(page); // Load the initial page (page 1)
    return () => {
    };
  }, [page]);

  const handleNextPage = () => {
    if (hasNext) {
      getAllFiles(page + 1, user.uid);
      setPage((prevPage) => prevPage + 1);
    }
  };

  const handlePrevPage = () => {
    if (page > 1) {
      getAllFiles(page - 1, user.uid); // Load the previous page
      setPage((prevPage) => prevPage - 1); // Update the page state
    }
  };

return (
    <div>
      <div className="d-flex flex-wrap">
        <title>EAS Document Browser</title>
        <br />
        <br />
        <h1 style={{ color: 'white' }}>Your Logs</h1>
      </div>
      <br />
      <Link href="/log/new" passHref>
        <Button className="btn btn-danger">Upload a Document</Button>
      </Link>
      <br />
      <br />
      <div className="d-flex flex-wrap flex-row">
{files && files.map((file) => <FileCard user={user} key={file.id} fileObj={file} onUpdate={getAllFiles} />)}
      </div>
      <div>
        <br />
        <br />
        {page > 1 && <Button onClick={handlePrevPage}>Previous Page</Button>}
        {hasNext && (
          <Button onClick={handleNextPage} className="justify-content-right">
            Next Page
          </Button>
        )}
      </div>
    </div>
  );
};

export default LogPage;

Django Models:

class Log(models.Model):
  
  user = models.ForeignKey(User, on_delete=models.CASCADE)
  dateStart = models.DateTimeField(max_length=20)
  dateEnd = models.DateTimeField(max_length=20)
  dateGenerated = models.DateTimeField(max_length=20)
  log_hash = models.CharField(max_length=255) 
  
  
  def __str__(self):
    return self.name

class Entry(models.Model):
  
  type = models.CharField(max_length=50)
  date = models.DateTimeField(default=timezone.now)
  details = models.CharField(max_length=500)
  zczc = models.CharField(max_length=200)
  log = models.ForeignKey(Log, on_delete=models.CASCADE)
  
def __str__(self):
  return self.name

class Files(models.Model):
  xml = models.FileField(upload_to='uploads')
  log = models.ForeignKey(Log, on_delete=models.CASCADE)
  entry = models.ForeignKey(Entry, on_delete=models.CASCADE)
  
  def __str__(self):
    return self.xml

File Viewset:

class CustomPageNumberPagination(PageNumberPagination):
    page_size = 50  # Number of entries per page
    page_size_query_param = 'page_size'
    max_page_size = 100

# class XMLView(ViewSet):
class XMLViewSet(viewsets.ViewSet):

    parser_classes = [MultiPartParser, FormParser]
    
    def create(self, request):
        user_uid = request.data['uid']
        print(user_uid)

    # Validate the user UID
        try:
            user = User.objects.get(uid=user_uid)
            print(user)
        except User.DoesNotExist:
            return Response({'error': f"User with UID {user_uid} not found."}, status=status.HTTP_400_BAD_REQUEST)

    # Access the uploaded XML file from the request object
        file = request.FILES.get('xml')
        print(file)

        if not file:
            return Response({'error': 'No XML file uploaded.'}, status=status.HTTP_400_BAD_REQUEST)

        # Read the contents of the file
        file_content = file.read().decode('utf-8')

        # Parse the XML content
        try:
            tree = ElementTree.fromstring(file_content)
        except ElementTree.ParseError:
            return Response({'error': 'Invalid XML format.'}, status=status.HTTP_400_BAD_REQUEST)

        # Access XML data
        for entry_data in tree.findall('entry'):
            log_date_start = tree.findtext('dateStart')
            log_date_end = tree.findtext('dateEnd')
            date_generated = tree.findtext('dateGenerated')

            # Generate a unique identifier based on log attributes
            log_identifier = f"{log_date_start}-{log_date_end}-{date_generated}"

            # Create or get the Log instance using the unique identifier
            log_instance, created = Log.objects.get_or_create(
                user=user,
                log_hash=log_identifier,
                defaults={
                    'dateStart': log_date_start,
                    'dateEnd': log_date_end,
                    'dateGenerated': date_generated
                }
            )

            if not created:
                # If the Log instance already exists, update the fields if necessary
                log_instance.dateStart = log_date_start
                log_instance.dateEnd = log_date_end
                log_instance.dateGenerated = date_generated
                log_instance.save()

            entry_type = entry_data.findtext('type')
            entry_date = entry_data.findtext('date')
            entry_details = entry_data.findtext('details')
            entry_zczc = entry_data.findtext('zczc')
            
            entry_date = datetime.strptime(entry_date, '%m/%d/%y %H:%M:%S').strftime('%Y-%m-%d %H:%M:%S')
            
            if not entry_zczc:
                entry_zczc = None
            try:
            # Create or update the Entry instance
                entry_instance, _ = Entry.objects.update_or_create(
                    log=log_instance,
                    type=entry_type,
                    date=entry_date,
                    details=entry_details,
                    zczc=entry_zczc
                )
            except Exception as e:
                print(f"Error creating Entry instance: {e}")
            

        return Response({'message': 'XML file successfully processed.'}, status=status.HTTP_200_OK)
    

    def retrieve(self, request, pk):
        try:
            file = Files.objects.get(pk=pk)
        except Files.DoesNotExist:
            return Response(status=status.HTTP_404_NOT_FOUND)
        serializer = FilesSerializer(file)
        return Response(serializer.data)

    ## pagination refactored by ID
    def list(self, request):
        paginator = CustomPageNumberPagination()
        
        # Order the queryset by a specific field (for example, 'id')
        files = Files.objects.order_by('id')

        result_page = paginator.paginate_queryset(files, request)
        serializer = FilesSerializer(result_page, many=True)
        return paginator.get_paginated_response(serializer.data)


    def destroy(self, request, pk):
        logXML = Files.objects.get(pk=pk)
        logXML.delete()
        return Response(None, status=status.HTTP_204_NO_CONTENT)
class FilesSerializer(serializers.ModelSerializer):
    log = LogSerializer()
    entry = EntrySerializer()

    class Meta:
        model = Files
        fields = ['id', 'log', 'entry']

I've tried adjusting things in both the front and back ends - adjusting proptypes being passed in both user and file forms, parameters in API calls as well as user authentication logic on the server side. It seems like it's probably a simple answer on the front end, so I am hoping another set of eyes on my code will help find the obvious issue that I'm overlooking.

0

There are 0 best solutions below